views:

115

answers:

2

I am looking into the unittest package, and I'm not sure of the proper way to structure my test cases when writing a lot of them for the same method. Say I have a fact function which calculates the factorial of a number; would this testing file be OK?

import unittest

class functions_tester(unittest.TestCase):
    def test_fact_1(self):
        self.assertEqual(1, fact(1))
    def test_fact_2(self):
        self.assertEqual(2, fact(2))
    def test_fact_3(self):
        self.assertEqual(6, fact(3))
    def test_fact_4(self):
        self.assertEqual(24, fact(4))
    def test_fact_5(self):
        self.assertFalse(1==fact(5))
    def test_fact_6(self):
        self.assertRaises(RuntimeError, fact, -1)
        #fact(-1)

if __name__ == "__main__":
    unittest.main()

It seems sloppy to have so many test methods for one method. I'd like to just have one testing method and put a ton of basic test cases (ie 4! ==24, 3!==6, 5!==120, and so on), but unittest doesn't let you do that.

What is the best way to structure a testing file in this scenario?

Thanks in advance for the help.

+3  A: 

You can put the asserts in a loop:

def test_fact(self):
    tests = [(1,1), (2,2), (3,6), (4,24), (5,120)]
    for n,f in tests:
        self.assertEqual(fact(n), f)
interjay
Edit: I originally thought this solution wouldn't work, because it only shows up as one test case in the command line, but it seems to work. Why does it only count as one test?
mellort
@mellort: The command line reports the number of `test_xxxx` functions it ran, not the number of asserts. Each function can have multiple asserts.
interjay
Note that this solution provides one test that will fail as soon as the first case fails, so you will lose any information about whether subsequent cases pass or fail. This may or may not be the behavior that you want.
Vicki Laidler
+3  A: 

I'd say your ways of doing it is generally fine (but read on).

You could, as interjay suggested, do a loop (and, incidentally, it only counts as one test because the unittest module counts the number of functions, not the number of asserts). But I assume you won't exhaustively try to test every number, or even all numbers within a very large interval. So looping won't save you much, and, especially in testing, you should aim at being explicit.

Having said that, you should test for a small number of subsequent numbers (say, 1 through 5), and then try to get a feel for where possible corner cases and failures points are. Say, test for 10, 100, 1000 (that is, change order of magnitude), negative numbers, zero etc.

BTW, watch out for your two last tests. The first of them doesn't mean much. fact(5) is different than a LOT of numbers (infinite numbers, actually). Test for the correct case, testing for the incorrect ones isn't productive.

def test_fact_5(self):
    self.assertFalse(1==fact(5))

The second one is badly named: "test_fact_6" makes me think you're testing fact(6). You should name it as something like "test_fact_minus_one", or at least "test_fact_negative_number".

def test_fact_6(self):
    self.assertRaises(RuntimeError, fact, -1)

Test naming is very important, both when you're debugging errors and when you refer back to the tests as documentation.

rbp