Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A DRY way of writing similar unit tests in python

I have some similar unit tests in python. There are so similar that only one argument is changing.

class TestFoo(TestCase):
    def test_typeA(self):
        self.assertTrue(foo(bar=TYPE_A))

    def test_typeB(self):
        self.assertTrue(foo(bar=TYPE_B))

    def test_typeC(self):
        self.assertTrue(foo(bar=TYPE_C))

    ...

Obviously this is not very DRY, and if you have even 4-5 different options the code is going to be very repetitive

Now I could do something like this

class TestFoo(TestCase):
    BAR_TYPES = (
        TYPE_A,
        TYPE_B,
        TYPE_C,
        ...
    )

    def _foo_test(self, bar_type):
        self.assertTrue(foo(bar=bar_type))

    def test_foo_bar_type(self):
        for bar_type in BAR_TYPES:
            _foo_test(bar=bar_type))

Which works, however when an exception gets raised, how will I know whether _foo_test failed with argument TYPE_A, TYPE_B or TYPE_C ?

Perhaps there is a better way of structuring these very similar tests?

like image 807
lukeaus Avatar asked Nov 18 '25 07:11

lukeaus


1 Answers

What are you trying to do is essentially a parameterized test. This feature isn't included in standard django or python unittest modules, but a number of libs provide it: nose-parameterized, py.test, ddt

My favorite so far is ddt: it resembles NUnit-JUnit style parameterized tests most, pretty lightweight, don't get in your way and does not require dedicated test runner (like nose-parameterized do). The way it can help you is that it modifies test name to include all parameters, so you would clearly see which test case failed by looking at a test name.

With ddt your example would look like this:

import ddt

@ddt.ddt
class TestProcessCreateAgencyOfferAndDispatch(TestCase):

    @ddt.data(TYPE_A, TYPE_B, TYPE_C)
    def test_foo_bar_type(self, type):
        self.assertTrue(foo(bar=type))

In such case names will look like test_foo_bar_type__TYPE_A (technically, it constructs it something like [test_name]__[repr(parameter_1)]__[repr(parameter_2)]).

As a bonus, it is much cleaner (no helper method), and you get three methods instead of one. The advantage here is that you can test various code paths in a method and get one test case per each path (but a certain amount of thinking is needed, sometimes it's better to have a dedicated test for some of code paths)

like image 186
J0HN Avatar answered Nov 20 '25 21:11

J0HN



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!