Search code examples
testingdjango-rest-frameworkbddpython-unittestdjango-channels

When to use BDD and when just unittests?


I have a task to write tests for future Django Channels+DRF project, don't ask why (we only have swagger documentation for now). So the tests have to test the user use cases (like scenario that may be complex). I have researched about that and found BDD. Here is the question, considering that our project later may have simple unit tests too what should I use, i.e. BDD seems decent but I think it may be excessive for use and may be there is a way of just writing unittests for user use case scenarious and I can get by with that. Does anyone have experience with that? It would be great if you provide articles and code examples.


Solution

  • Scenarios are a bit different to use-cases. A use-case often covers several capabilities. For instance, in the simple laundry use-case shown here, a housekeeper does several things when performing a wash:

    • washes each load
    • dries each load.
    • folds certain items
    • irons some items

    All of these go into the "weekly laundry" use-case.

    A scenario in BDD is much more fine-grained. It describes one capability taking place in a particular context or set of contexts. So for instance you might have:

    Given the weekly laundry has been washed and dried
    And it contains several sheets
    And some underpants
    When the housekeeper does the folding
    Then the sheets should be folded
    But the underpants should not.
    

    You can see that we've skipped a couple of the capabilities. This scenario is focused on the capability of folding, and shows how a well-behaved housekeeper would do it. Washing and drying would have to be covered in separate scenarios.

    So that's the difference between a use-case and a scenario. Now let's look at a unit test.

    When we write code, we don't write it all in one big class or function. We split it up into small pieces. In the same way that a scenario describes an example of the behaviour of the system from the perspective of the users, a unit test describes the behaviour of a class or other small piece of code from the perspective of its users - usually other classes!

    So let's imagine that we're on a car purchasing site. We have several capabilities:

    • Authentication
    • Searching for cars
    • Purchasing a car
    • Listing a car
    • Removing a car from the list

    Each of these will have lots of different classes making it up. Even searching for a car could involve a front-end, a search component, a database of cars, a persistence layer, a webserver, etc.. For each piece of code, we describe the behaviour of that code.

    (BDD actually started out at this level; with examples of how classes behave - JBehave was intended to replace JUnit. But JUnit got better and we didn't need this bit any more. I still find it helpful to think of these as examples rather than tests.)

    Typically I'll have both scenarios and unit tests in my codebase; one set of them looking from a user / stakeholder perspective at the whole system, and the other set describing my classes in finer detail.

    The scenarios help me show how the system behaves and why it's valuable. The unit tests help me drive out good design and separate responsibilities. Both of them provide living documentation which helps to keep the system maintainable and make it easier for newcomers to come on board.

    Generally this is how I program:

    • I have a rough idea of what I want to achieve
    • I talk to someone about it and write down some scenarios
    • If we don't quite know what we're looking for, I'll get something working (a spike)
    • Once we understand better what we're looking for, I automate the scenario first
    • I take the simplest case and start writing the UI
    • When the UI needs another class to work, I write some examples of how that code should work (unit tests) first
    • Then I write the code (or refactor it, because spikes are messy)
    • When that code needs another class to work, I write some examples of it
    • If I don't have code that's needed at any point in my unit tests, I use mocks.

    Generally we keep the scenarios and the unit tests in different places.

    You can see some examples of how I've done this here. It's a tetris game with scenarios which automate the whole game through the UI, and lower-level unit tests which describe the behaviour of particular pieces like the heartbeat which drops the shapes.

    Having said that - if your codebase is very simple, you can probably get away with just the scenarios or just the unit tests; you might not need both. But if it starts getting more complex, consider refactoring and adding whatever you need. It's OK to be pragmatic about it, as long as it's easy to change.