Python Unit Testing – How Much to Break Up Unit Tests

pythonunit testing

I recently learned about unit tests and TDD and have started going back to add unit tests to my code (and thank god, it's proven that some things I've written are much more broken than I thought).

That being said, I'm having a hard time figuring out how much (or little) should go into each test. For example, consider a class I wrote with several methods. Right now I do something like

class test_myclass(unittest.TestCase):

    def setUp(self): 
        ...
    def test_func1(self):
        ...
    def test_func2(self):
        ...
    etc
    def tearDown(self):
        ...

But what if each (or at least some) method can fail in a lot of different ways, or has a lot of edge cases? Then I'm stuck doing something like

def test_func1(self):
    self.assertEqual(func1(working_input), expected_value)
    self.assertRaises(Some_error, func1, args)
    self.assertRaises(Some_other_error, func1, args)
    etc

And it could get pretty hairy, and rather unclear where things are failing. So I thought I might break it up more, and devote a class to each function (that has enough test cases to be worth it) but that seems like a lot of work, and more time than I should probably be spending on testing.

I also considered writing multiple tests for a given method, such as

def test_func1(self):
    self.assertEqual(func1(working_input), expected_val)
def test_func1_bad_input_1(self):
    self.assertRaises(error, func1, bad_args)
def test_func1_bad_input_2(self):
    self.assertRaises(error2, func1, bad_args_2)
...

And this seems cleaner than a unit testing class per method, but still seems like it might be to little in any given test.

How much testing is too much?

While my example was given in python, I'm open to language agnostic answers

Best Answer

In an ideal world, a single unit test has only one thing that can cause it to fail, and that one thing can only cause that test to fail.

I'm not sure I've seen that ever be true though... A more attainable approach is that a single unit test tests a single path though a function. The concept of code coverage is helpful here, but the gist of it is that any decision Your code makes represents (at least) a different path. Some paths may be too difficult (or actually impossible) to unit test, but that is the level of granularity to work towards.

The benefit is that when a test fails, you spend less time and effort figuring out why.

And yes, it seems like a bit more work, but once you're used to it, the tests are quick and easy to write - and they'll save you tons of time.

Related Topic