A pythonic way to code self writing test methods

I have the following test class defined. It uses exec which generally I dislike.

class FubarTest(unittest.TestCase):
    lst = [(True, True),
           (False, False)]
    for t in lst:
        function = """def test_{}_is_{}(self):
        self.assertTrue({} is {})
        """.format(t[0], t[1], t[0], t[1])
        exec function

When I run it (via py.test but that should not matter), I get this:

============================= test session starts ==============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /usr/bin/python
plugins: capturelog, cov, twisted, xdist

model/test/test_hframe5.py <- <string>:1: HFrame5Test.test_False_is_False PASSED
model/test/test_hframe5.py <- <string>:1: HFrame5Test.test_True_is_True PASSED
=========================== 2 passed in 0.49 seconds ===========================

So that the test are automatically created and have sensible names so if something fails, you know by the name of the method what has failed.

Clearly this is a much simplified example of what I want to do. I have a lot of test that will look like "do X, check that Y is True and Z is False" which could be easily coded with the above method. I could write three dozen copy-and-pasted methods but that just feels wrong -- breaking DRY.

Is there a more pythonic way of writing this code?

Upvotes: 3

Views: 163

Answers (4)

aquavitae
aquavitae

Reputation: 19154

If you use nose, you can use a test generator:

class FubarTest(object):

    def check_is(self, a, b):
        assert {} is {}

    def test_is(self):
        lst = [(True, True), (False, False)]
        for a, b in lst:
            self.check_is(a, b)

Upvotes: 1

ecatmur
ecatmur

Reputation: 157444

You want parametrized tests. For example, with parameterizedtestcase:

from parameterizedtestcase import ParameterizedTestCase

class MyTests(ParameterizedTestCase):
    @ParameterizedTestCase.parameterize(
        ("value", "expected"),
        [
            (True, True),
            (False, False),
        ]
    )
    def test_identical(self, value, expected):
        self.assertTrue(value is expected)

Using py.test's parametrization framework:

import pytest

@pytest.mark.parametrize(
    ("value", "expected"),
    [
        (True, True),
        (False, False),
    ]
)
def test_identical(value, expected):
    assert value is expected

Upvotes: 1

Duncan
Duncan

Reputation: 95722

You almost certainly don't need to use exec. Just use 'setattr' to add a suitable function to the class, and use a nested scope to bind the test values. For example, I think this should work:

class FubarTest(unittest.TestCase):
    pass

def createTest(cls, arg1, arg2):
    def test(self):
        self.assertTrue(arg1 is arg2)
    testname = "test_{}_is_{}".format(arg1, arg2)
    setattr(cls, testname, test)

def createTests():
    lst = [(True, True),
        (False, False)]
    for a,b in lst:
        createTest(FubarTest, a, b)

createTests()

Upvotes: 1

U2EF1
U2EF1

Reputation: 13279

I would usually err on the side of more test than less test, really no matter how it gets done. You're probably fine.

Upvotes: 1

Related Questions