Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • Testing samples of the object: Making a few equality assertions based on a subset of the keys. This will sacrifice the thoroughness of the test for the sake of code elegance.

  • Defining the object in a separate module: Writing the lengthy 40-50 lines of code in a separate .py file, importing the module in the test and then make the equality assertion. This will make the test short and concise but I don't like having a separate file as a supplement to a test; the object definition is part of the test after all.

  • Defining the object inside the test function: This is the trivial solution which I wish to avoid. My tests are pretty simple and straightforward and the lengthy definition of that object doesn't fit.

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?

like image 835
snakile Avatar asked Aug 27 '12 09:08

snakile


2 Answers

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.

like image 63
Constantinius Avatar answered Oct 02 '22 10:10

Constantinius


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.

like image 33
James Henstridge Avatar answered Oct 02 '22 12:10

James Henstridge