snakile
snakile

Reputation: 54541

A long definition of an object inside a Python unit test

I'm unit testing my application. What most of the tests do is calling a function with specific arguments and asserting the equality of the return value with an expected value.

In some tests the expected return value is a relatively big object. One of them, for example, is a dictionary which maps 5 strings to lists of tuples. It takes 40-50 repetitive lines of code to define that object, but that object is an expected value of one of the functions I'm testing. I don't want to have a 40-50 lines of code defining an expected return value inside a test function because most of my test functions consist of 3-6 lines of code. I'm looking for a best practice for such situations. What is the right way of putting lengthy definitions inside a test?

Here are the ideas I was thinking of to address the issue, ranked from the best to the worst as I see it:

Maybe I'm too obsessed with clean code, but I like none of the above solutions. Is there another common practice I haven't thought of?

Upvotes: 7

Views: 858

Answers (2)

James Henstridge
James Henstridge

Reputation: 43949

It really depends on what you want to test.

If you want to test that a dictionary contains certain keys with certain values, then I would suggest separate assertions to check each key. This way your test will still be valid if the dictionary is extended, and test failures should clearly identify the problem (an error message telling you that one 50-line long dictionary is not equal to a second 50 line long dictionary is not exactly clear).

If you really do want to verify that the dictionary contains only the given keys, then a single assertion might be appropriate. Define the object you are comparing against where it is most clear. If defining it in a separate file (as Constantinius's answer suggests) makes things more readable then consider doing that.

In both cases, the guiding principle is to only test the behaviour you care about. If you test behaviour you don't care about, you may find your test suite more obstructive than helpful when refactoring.

Upvotes: 1

Constantinius
Constantinius

Reputation: 35069

I'd suggest using a separation of testing code and testing data. For this reason I usually create an abstract base class which contains the methods I'd like to test and create several specific test case classes to tie the methods to the data. (I use the Django framework, so all abstract test classes I put into testbase.py):

testbase.py:

class TestSomeFeature(unittest.TestCase):
    test_data_A = ...

    def test_A(self):
        ... #perform test

and now the implementations in test.py

class TestSomeFeatureWithDataXY(testbase.TestSomeFeature):
    test_data_A = XY

The test data can also be externalized, e.g a JSON file:

class TestSomeFeatureWithDataXYZ(testbase.TestSomeFeature):
    @property
    def test_data_A(self): 
        return json.load("data/XYZ.json")

I hope I made my points clear enough. In your case I'd strongly opt for using data files. Django supports this out-of-the-box by using test fixtures to be loaded into the database prior executing any tests.

Upvotes: 3

Related Questions