Python testing with nose by example
I believe I'm not the first developer who grew tired of all the setup required to make unit tests work with the unittest standard module. Luckily somebody decided to create nose.
Nose relies in healthy defaults rather than in explicit declarations. I#ll make this document example driven.
Installing nose
It's mainly a matter of getting the last version and making sure that the nosetests file is included in the PATH.
Examples
Here's the first example. Simply create a file and include a function whose name include the "test" token and run nose on them passing the file name as an argument. As you can see the test method was run and eq_ (similar to unittest's assertEquals) checked that it's two parameters were equal.
1 #testset.py
2 from nose.tools import eq_
3
4 def test_sum():
5 eq_(2+2,4)
6
C:\cesar\nosefiles>nosetests testset.py . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
In the following example I introduce a failing testing method. This time nose reports 2 test ran, one failed.
1 #testset.py
2 from nose.tools import ok_, eq_
3
4 def test_sum():
5 eq_(2+2,4)
6
7 def test_failing_sum():
8 ok_(2+2 == 3, "Expected failure")
9
C:\cesar\nosefiles>nosetests testset.py
.F
======================================================================
FAIL: testset.test_failing_sum
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Python26\lib\site-packages\nose-0.11.4-py2.6.egg\nose\case.py", line 186, in runTest
self.test(*self.arg)
File "C:\Documents and Settings\desales\Desktop\nose\testset.py", line 8, in test_failing_sum
ok_(2+2 == 3, "Expected failure")
File "C:\Python26\lib\site-packages\nose-0.11.4-py2.6.egg\nose\tools.py", line 25, in ok_
assert expr, msg
AssertionError: Expected failure
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)Let's suppose you made the test_failing_sum fail on purpose, perhaps because you are working on Test Driven Development. The nottest annotation will make nose ignore the decorated method. As a side note, the -v (verbose) flag will indicate nose to output the status for each test.
1 #testset.py
2 from nose.tools import ok_, eq_, nottest
3
4 def test_sum():
5 eq_(2+2,4)
6
7 @nottest
8 def test_failing_sum():
9 ok_(2+2 == 3, "Expected failure")
10
C:\cesar\nosefiles>nosetests -v testset.py testset.test_sum ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
You can test classes as well. Please notice that in the following example the ignored method is not run by nose because it's not named after test. There's not even need to create an instance or a main method.
1 from nose.tools import ok_, eq_
2
3 def test_sum():
4 eq_(2+2,4)
5
6 def test_subs():
7 ok_("Unexpected failure", 6-2 == 4)
8
9
10 class TestSuite:
11 def test_mult(self):
12 eq_(2*2,4)
13
14 def ignored(self):
15 eq_(2*2,3)
16
C:\cesar\nosefiles>nosetests -v testset.py testset.TestSuite.test_mult ... ok testset.test_sum ... ok testset.test_subs ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.000s OK
The istest decorator will mark a method as runnable by nose, even if it's name doesn't include the test substring:
1 from nose.tools import ok_, eq_, istest
2
3 def test_sum():
4 eq_(2+2,4)
5
6 def test_subs():
7 ok_("Unexpected failure", 6-2 == 4)
8
9 class TestSuite:
10 def test_mult(self):
11 eq_(2*2,4)
12
13 @istest
14 def ignored(self):
15 eq_(2*2,4)
16
C:\cesar\nosefiles>nosetests -v testset.py testset.TestSuite.ignored ... ok testset.TestSuite.test_mult ... ok testset.test_sum ... ok testset.test_subs ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.000s OK
There's no need to have all of your tests in a single file. In the following example I have split the tests in two files and passed both names for nose to test. The same result can be achieved by running nose without passing any file name as argument, in which case all files named after test will be run by default.
1 #test_set.py
2 from nose.tools import ok_, eq_, istest
3
4 def test_sum():
5 eq_(2+2,4)
6
7 def test_subs():
8 ok_("Unexpected failure", 6-2 == 4)
9
10
11 #complex_test_set.py
12 from nose.tools import ok_, eq_, istest
13
14 class TestSuite:
15 def test_mult(self):
16 eq_(2*2,4)
17
18 @istest
19 def ignored(self):
20 eq_(2*2,4)
21
C:\cesar\nosefiles>nosetests -v test_set.py complex_test_set.py complex_testset.TestSuite.ignored ... ok complex_testset.TestSuite.test_mult ... ok testset.test_sum ... ok testset.test_subs ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.016s OK C:\cesar\nosefiles>nosetests -v complex_testset.TestSuite.ignored ... ok complex_testset.TestSuite.test_mult ... ok testset.test_sum ... ok testset.test_subs ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.016s OK
Tests can also be configured in a file:
1 #test_set.py
2 from nose.tools import ok_, eq_, istest
3
4 def test_sum():
5 eq_(2+2,4)
6
7 def test_subs():
8 ok_("Unexpected failure", 6-2 == 4)
9
1 #complex_test_set.py
2 from nose.tools import ok_, eq_, istest
3
4 class TestSuite:
5 def test_mult(self):
6 eq_(2*2,4)
7
8 def ignored(self):
9 eq_(2*2,4)
10
#config.ini
[nosetests]
where=C:\cesar\nosefiles
#test only some modules
tests=testset.py,
complex_testset.py
C:\cesar\nosefiles>nosetests -s -v -c config.ini
testset.test_sum ... ok
testset.test_subs ... ok
complex_testset.TestSuite.ignored ... ok
complex_testset.TestSuite.test_mult ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.016s
OKThere's even the possibility to specify which functions/methods will be run, both in the command line or in a configuration file:
1 #test_set.py
2 from nose.tools import ok_, eq_, istest, nottest
3
4 def test_sum():
5 eq_(2+2,4)
6
7 def test_subs():
8 ok_("Unexpected failure", 6-2 == 4)
9
1 #complex_test_set.py
2 from nose.tools import ok_, eq_, istest, nottest
3
4 class TestSuite:
5 def test_mult(self):
6 eq_(2*2,4)
7
8 def ignored(self):
9 eq_(2*2,4)
10
#config.ini
[nosetests]
where=C:\cesar\nosefiles
#test only some modules
tests=testset.py:test_sum,
complex_testset.py:TestSuite.ignored
C:\cesar\nosefiles>nosetests -s -v -c config.ini
testset.test_sum ... ok
complex_testset.TestSuite.ignored ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OKAll updates to this tutorial will be posted in my blog
Created by Cesar C. Desales, November 2011.
