0

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?

  • Creating a custom setup function to use inside each test case
  • Using setUp() with global variables
  • Using setUp() with class or instance variables
7
  • The recommended strategy is calling the function to get the return value. It is unclear to me how setUp and global, class or instance variables are relevant here. Commented Jul 1, 2019 at 21:29
  • setUp returns None, but I need a setup function that returns a value. The problem is whether to not use setUp at all and make your own setup function and call it in every test method or use setUp but set some global variables in it. Commented Jul 1, 2019 at 22:52
  • I have no idea why you would want to "set some global variables". Commented Jul 2, 2019 at 7:37
  • So as to have common objects needed for each test, say lists, but each list would contain different objects depending on the test. Commented Jul 5, 2019 at 20:48
  • Why would you want them to be common? Commented Jul 5, 2019 at 21:59

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! This looks nice, but what if mytestlist had to have different elements for each test, depending on the requirements?
This answer covers that. If you have something you need to change in each test, then make it local to the test. If something will be used in multiple tests add it to setUp. I feel like that is pretty clear in my answer. A list is no longer the same list if you change its elements.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.