genexpr
genexpr

Reputation: 91

How to properly use a per-test setup function that returns a value in unittest?

For each of my tests in a unittest.TestCase suite, I need to do some setup: run a function that returns a different value for each test (depending on some properties of each test that are passed as arguments to the setup function).

There is the setUp() hook that I could use, but it does not return a value and does not accept arguments. But let's say arguments aren't important in this case.

What strategy is recommended?

Upvotes: 0

Views: 4813

Answers (2)

Dirk Herrmann
Dirk Herrmann

Reputation: 5939

Note my destinction between setup and Setup below: Each test has to so some setup activities, but these are not necessarily put into the Setup method.

My preference is that each test function should be easily understood by itself. I don't want to look at test code wondering "where does this value suddenly come from" or the like. Which means, I avoid a common Setup method but instead use helper functions / methods that also have descriptive names. To be more precise:

  • If a test case needs a specific setup, I normally embed it directly into the test.
  • If a subset of the test cases have an identical or similar setup, I consider creating a helper method to extract the common parts. The name of that helper method should be descriptive, like makeTrafficLightInGreenState for a helper factory to produce a specific type of traffic light object. In this example I could then also have makeTrafficLightInRedState for a different group of tests. I find this preferrable over a common Setup method which just creates both the green and the red traffic light: In the end you get confused which part of Setup is relevant for which test. Certainly, when writing your helper methods, you are also free to give them parameters, which could in the traffic light example result in 'makeTrafficLightInState(green)'. But, which approach to choose here is situation specific.
  • Even if all tests have something in common, like the creation of a certain object, I prefer putting this in a descriptively named helper method rather than relying on the implicit call to Setup.
  • Sometimes I also use Setup, but only for activities that are not necessary for understanding the logic of the test cases. For example, if there is some original state that needs to be preserved at the beginning of the test cases and to be restored afterwards, this could be put into Setup without any negative impact on the readability of the individual test cases.

In your case, you seem to already have a function with parameters that is suited to deliver test case specific data. I would simply call that function from each test case that needs it. Or, if calling that function is not simple (you have to create additional objects as arguments for it or the like), again put it into a helper method.

Upvotes: 0

d_kennetz
d_kennetz

Reputation: 5359

I follow the general strategy of, if a variable will be used in multiple test functions, I define it in the setUp(). If it will only be used once local to a specific function, define it in that function.

Take the following example:

Say I have a python module in package program called list_utils.py and in list_utils.py I have the following functions:

def list_to_string(mylist):
    """ Takes a list of strings and joins them into a single string.
    """
    return ' '.join(mylist)

def list_extender(mylist, extend_item):
    return mylist.extend(extend_item)

Then I setup my unittest script with specifying mytestlist because it will be used in multiple test functions:

from program import list_utils as lu

class TestListUtils(unittest.TestCase):
    """
    A subclass of unittest to test list_utils.py
    """
    def setUp(self):
        self.mytestlist = ['Hi', 'there']

    def test_list_to_string(self):
        """
        Ensures my list is converted to string
        """
        self.assertTrue(isinstance(lu.list_to_string(self.mytestlist), string))
    def test_list_extender(self):
        """
        Ensures list is extended when argument is passed.
        """
        mylocalvariable = 'Adam'
        self.assertTrue(lu.list_extender(self.mytestlist, mylocalvariable)[-1] == 'Adam')

    def tearDown(self):
        pass

if __name__ == '__main__':
    unittest.main()

You see that for list_extender I passed in mylocalvariable because I would only use it in the scope of that function, but mytestlist was defined in setUp because I used it multiple times. By following this general approach, you shouldn't bloat your setUp too much, and you also won't have to re-instantiate variables at each specific unittest if they will be used multiple times.

Upvotes: 2

Related Questions