I've been meaning to write a simple primer for unit testing in python ever since I realized how invaluable writing tests are. You must be thinking that everyone knows to test they're code. I did too, but I didn't do it. It seemed hard and I was lazy. Fortunately, its really easy to get started.
This blog post is about how to get started with
nose to write unit
tests for your python code with as little friction as possible.
If you have no experience writing tests, I fully empathize with you. I previously only wrote tests when it was 'necessary', which meant I never wrote them. Consequently, during my internships, I found out how little experience I had writing tests and testable code. However, once I was over the initial hurdle, it was easy to see the benefits and flexibility that having tests affords you.
I figured as with most things, this is a skill that I could get better at by doing repeatedly. I started writing tests for most of my code, even one-off class assignments and projects that didn't really care too much about them.
The setup is really easy. Just
pip install nose
The great thing about
nose, is how easy it is to write and run tests.
nose adds a
nosetests command that you can invoke to run your
tests inside the folder you're currently at.
- files or folders that contain
/[Tt]est/in their name, eg.
- within these files, functions and classes with
/[Tt]est/in their name will be run as tests. eg.
Test files and writing your first test
Where you put your tests is a matter of preference, I prefer to have my tests in the same place as my files for ease of locating them and importing the code to be tested.
If I have
/path/to/project/src/model.py, I would have a corresponding
/path/to/project/src/test_model.py. So all tests for
model.py will go into
Inside your test files, you simply import the code you're testing and test it. I'll be using examples from one of my class assignments so feel free to refer to it for the full source.
Here is an example:
m = model.Model()
assert m.total_count() == 0
assert m.total_count() == (5 + 4)
assert m.total_count(include_smoothing=False) == 5
In the above example, I import the
model file and test the
method of the
Model object before and after incrementing the 'grams' in the
Saving this file and running
nosetests on the command line will run
Some other things about nose
If you've written tests before, or at least heard of them, you'll know that the example above is really simple and you probably will need a couple more things to get going.
Chances are, you're writing a couple of tests in each test file that are highly
nose makes it really easy to write
for your tests:
# setup here..
# teardown here..
Simply name them as such and the test runner will run the functions before and after each test. (There are other acceptable variants, which I'm leaving out that will be run by the test runner).
In the example above, I used the built-in
assert function in python. However,
when the test fails, the error message isn't really helpful, it'll say that it
True but got
nose provides a nice
nose.tools._eq_ that takes two values and compares them
== operator. Upon failure, it gives a nice message, something like
"expected 5 but was given 4", helping you to indentify and fix the source of the
broken test quickly.
Other bells and whistles
For the sake of making this as simple as possible, I have glossed over
nose's functionalities. You probably will use some of them eventually
but they are not necessary to start writing simple tests and get into the habit
of writing testable code. Trust me, it gets easier.
- Using a test class instead of test functions
- Specific setup and teardown functions for specific test functions
- Running only some tests (and not all of them)
- Testing that an exception was raised in a test
Hopefully you feel like writing unit tests in python is really simple after
reading this. If you find yourself in the need for mocking and stubing objects
in your tests, I highly recommend the
that is model after the java library of the same name. It integrates seamlessly
nose and is really intuitive to use.