Test Python : Applying Unit and BDD Testing

When you’re just getting started with automating your Python testing, there’s a lot of questions. You’ll probably see people talk about unit testing, TDD or Test-Driven Development, and BDD or Behavior-Driven Development. But which one of them is the best approach? Can you use all of them?

I’ve talked to a number of Python developers, and there seems to be some confusion about all this. So, let’s take a look at Unit testing and BDD, and fix some of the common misconceptions about them out there.

Unit testing

A unit test focuses on a single “unit of code” — usually a function in an object or module. By making the test specific to a single function, the test should be simple, quick to write, and quick to run. This means you can have many unit tests, and more unit tests means more bugs caught. They are especially helpful if you need to change your code, as you can safely do so and trust that any other code will not break.

A unit test should be isolated from dependencies — for example, no network access and no database access. There are tools that can replace these dependencies with fakes you can control. This makes it trivial to test all kinds of scenarios that would otherwise require a lot of setup. For example, imagine having to set up an entire database just to run a test. Ugh, no thanks.

Unit tests in Python

Python unittest module used to be called PyUnit, due to it’s legacy as a xUnit style framework.
It works much the same as the other styles of xUnit, and if you’re familiar with unit testing in other languages, this framework (or derived versions), may be the most comfortable for you

The standard workflow is:
1. You define your own class derived from unittest.TestCase.
2. Then you fill it with functions that start with ‘test_’.
3. You run the tests by placing unittest.main() in your file, usually at the bottom.

import unittest
from unnecessary_math import multiply
class TestUM(unittest.TestCase):
def setUp(self):
pass

def test_numbers_3_4(self):
self.assertEqual( multiply(3,4), 12)
if __name__ == '__main__':
unittest.main()

But this is just an example of what a unit test looks like.

Some people think that any automated test is a unit test. This is not true. There are different types of automated tests. They should not be confused with each other, as each has its own purpose.

  • Unit tests: A single piece of code (usually an object or a function) is tested, isolated from other pieces
  • Integration tests: Multiple pieces are tested together, for example testing database access code against a test database
  • Acceptance tests (also called Functional tests): Automatic testing of the entire application, for example using a tool like Selenium to automatically run a browser.

BDD

BDD — Behavior-Driven Development — is perhaps the biggest source of confusion. When applied to automated testing, BDD is a set of best practices for writing great tests. BDD can, and should be, used together with unit testing methods.

The idea is to not only test your code at the granular level with unit tests, but also test your application end to end, using acceptance tests.

BDD with Python

We will introduce this style of testing with the use of the Lettuce testing framework.

The process can be simply defined as:

  • Write a failing acceptance test
  • Write a failing unit test
  • Make the unit test pass
  • Refactor
  • Make the acceptance test pass

Rinse and repeat for every feature, as is necessary.

Lets start with lettuce

Make sure you’ve got Python installed and then run from the terminal:

pip install lettuce

Build the directory tree bellow such as the files zero.feature and steps.py are empty.


| tests
| features
- zero.feature
- steps.py

describe behaviour

Start describing the expected behaviour of factorial in zero.feature using English:

Feature: Compute factorial
In order to play with Lettuce
As beginners
We'll implement factorial

Scenario: Factorial of 0
Given I have the number 0
When I compute its factorial
Then I see the number 1

define steps in python

Now let’s define the steps of the scenario, so Lettuce can understand the behaviour description. Create thesteps.py file which will contain python code describing the steps.

from lettuce import *
@step('I have the number (\d+)')
def have_the_number(step, number=1):
world.number = int(number)
@step('I compute its factorial')
def compute_its_factorial(step):
world.number = factorial(world.number)
@step('I see the number (\d+)')
def check_number(step, expected=1):
expected = int(expected)
assert world.number == expected, \
"Got %d" % world.number

Run and watch

user@machine:~/projects/mymath/tests$ lettuce

For example lets number is one and its factorial is also one So output of factorial function must we one else test false here we only testing output base on given input .Here implementation detail of factorial() function irrelevant to the behavior of function. Therefore it should not have any bearing on the test. The only reason we wrote the test like this is because we were thinking of the implementation, not of the behavior.

BDD suggests to test behaviors, so instead of thinking of how the code is implemented, we spend a moment thinking of what the scenario is. Typically you phrase BDD tests in the form of “it should do something”.

Conclusion

Unit Testing gives you the what. Behavior Driven-Development gives you the how. Although you can use each individually, you should combine them for best results as they complement each other very nicely.