Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Unittest Modularity vs Readability

I have a Python unittest, with some tests having the same type object tested. The basic outline in one test-class is:

class TestClass(unittest.TestCase):
    def setup(self):
        ...

    def checkObjects(self, obj):
        for i in [...values...]:
            self.assertEqual(starttags(i,obj), endtags(i,obj))

    def testOne(self):
        #Get object one.
        checkObjects(objone)

    def testAnother(self):
        #Access another object.
        checkObjects(another)

    ... various tests for similar objects.

Although it's modular, I noticed that any failures will give an error like AssertionError: number != anothernumber, and the line of code generating the error, self.assertEqual(starttags(i,obj), endtags(i,obj)). If I had listed the tests out instead of placing in a for loop, I would have something like:

self.assertEqual(starttags(value1,obj), endtags(value1,obj))
self.assertEqual(starttags(value2,obj), endtags(value2,obj))

Which shows exactly which case causes the error, but is copy-paste code, which I thought was generally not recommended. I noticed the problem recently when a contributor reworked a more clean unit-test, that unfortunately would give little debug-info on assertion failures. So, what is the best-practice in these cases? Something like a list of tuples, fed into a for-loop with assertEquals is "cleaner", but copy-paste with different values on different lines gives useful stack traces.

like image 385
NoBugs Avatar asked Feb 14 '12 04:02

NoBugs


1 Answers

If by cleaner you mean less code, then this kind of cleaner code is not always more readable code. In fact, it usually is less readable (especially when you come back to it). You can always go with fancy refactorings but you need to know when to stop. In longer run, it's always better to use more obvious, simple code than trying to squeeze one less line of code for artificial gains - not just in terms of unit testing.

Unit tests go with their own rules. For example they usually allow different naming conventions than what your regular code standards say, you hardly ever document it - they're kind of special part of your codebase. Also, having repeated code is not that uncommon. Actually, it's rather typical to have many similar looking, small tests.

Design your tests (code) with simplicity in mind

And at the moment, your tests are confusing even at the stage of writing them - imagine coming back to that code in 3 months from now. Imagine one of those tests breaking as a result of somebody else doing some change in some other place. It won't get any better.

Design your tests in such a way, that when one of them breaks, you immediately know why it did so and where. Not only that - design them in such a way, that you can tell what they are doing in a blink of an eye. Having for loops, ifs and basically any other kind of control flow mechanism (or too extensive refactoring) in test code, usually results in one question springing to your mind - What are we doing here? This is the kind of question you don't want to find yourself asking.

To summarize this longish post with words of people smarter than myself:

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

-Martin Fowler et al, Refactoring: Improving the Design of Existing Code, 1999

Do yourself a favor and stick to that rule.

like image 133
k.m Avatar answered Sep 28 '22 04:09

k.m