Revision 4 as of 2005-09-13 18:11:13

Clear message

py.test is an alternative, more Pythonic way of writing your tests. The best part is, the overhead for writing unit tests is practically zero!

For example, assume you have a prime generator in module prime.py, and you'd want to write a simple test for it (if you don't know it, a prime is a non-negative integer being divisible only by 1 and itself, and the first prime is 2):

   1 from prime import PrimeGenerator
   2 
   3 def test_first_primes():
   4     pg = PrimeGenerator()
   5 
   6     assert pg.first_primes(6) = [2, 3, 5, 7, 11, 13]

That's it - you have a working test suite you can run by giving the test file as argument for py.test.

But now you'd like to extend the test a bit further - add a second test for testing primality:

   1 from prime import PrimeGenerator
   2 
   3 def test_first_primes():
   4     pg = PrimeGenerator()
   5 
   6     assert pg.first_primes(6) = [2, 3, 5, 7, 11, 13]
   7 
   8 def test_primality():
   9     pg = PrimeGenerator()
  10     
  11     known_primes = (17, 19, 23, 29, 31)
  12     known_nonprimes = (21, 27, 33, 49)
  13     
  14     for p in known_primes:
  15          assert pg.isprime(p)
  16     for np in known_nonprimes:
  17          assert not pg.isprime(np)

It is nice and everything, but now there are two issues. First, if you have many tests, it is bothersome to write pg = PrimeGenerator() in every method - ok, not that bothersome, but in programming, being lazy in particular way is not only elegant, but saves your from errors and other problems in the future, trust me.

The other issue is that if your method isprime() is not working correctly, you see the error, but you don't see what number triggered the error. Let's fix these two problems at next attempt.

   1 from prime import PrimeGenerator
   2 
   3 class TestPrime:
   4 
   5     def setup_class(self):
   6         self.pg = PrimeGenerator()        
   7 
   8     def test_first_primes(self):
   9         assert pg.first_primes(start=5, 4) = [5, 7, 11, 13]
  10 
  11     def _isprime(self, n, expected):
  12         assert self.pg.isprime(n) == expected
  13 
  14     def test_isprime(self):
  15         known_primes = (17, 19, 23, 29, 31)
  16         known_nonprimes = (21, 27, 33, 49)
  17 
  18         for p in known_primes:
  19              yield self._isprime, p, True
  20 
  21         for np in known_nonprimes:
  22              yield self._isprime, np, False            

What happen, you say? Jokes aside, you should notice that first off, you no longer need to setup prime generator instance in every method - setup_class takes care of instantiating needed object. But test_isprime() looks now a tad more exotic - it uses generator expression yield for generating tests on the fly. Now if your prime generator fails, you see the exact error - ie. what argument failed to give correct, expected result.

You may now wonder - please do! - how py.test knows which methods to run. Well, there has to be some magic going behind the scenes because you don't see any testsuite.run(method) or testsuite.add(class_instance) etc. The idea is neat: simply, every class starting with Test and every method starting with test_ is treated specially and considered to be part of unit test suite. Also, assert is magic, but you don't need to know that... it is so to give you better error messages.

There are more nice features in py.test, which make it a very nice TDD tool:

* tests are run in the order you specify them, making tests both deterministic and predictable * you can ask py.test abort on first error using -x option * running tests will start immediately upon collecting them * you can start py.test as daemon, constantly monitoring your modules for changes and running tests when they occur

So, if you consider giving TDD (Test Driven Development) a try, please try py.test. It makes writing unit tests more fun :*)

More information:


UnitTests

Unable to edit the page? See the FrontPage for instructions.