Search code examples
pythonunit-testingtestingassertarrange-act-assert

What is the correct way to report an error in a Python unittest in the setUp method?


I've read some conflicting advice on the use of assert in the setUp method of a Python unit test. I can't see the harm in failing a test if a precondition that test relies on fails.

For example:

import unittest

class MyProcessor():
    """
    This is the class under test
    """

    def __init__(self):
        pass

    def ProcessData(self, content):
        return ['some','processed','data','from','content'] # Imagine this could actually pass

class Test_test2(unittest.TestCase):

    def LoadContentFromTestFile(self):
        return None # Imagine this is actually doing something that could pass.

    def setUp(self):
        self.content = self.LoadContentFromTestFile()
        self.assertIsNotNone(self.content, "Failed to load test data")
        self.processor = MyProcessor()

    def test_ProcessData(self):
        results = self.processor.ProcessData(self.content)
        self.assertGreater(results, 0, "No results returned")

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

This seems like a reasonable thing to do to me i.e. make sure the test is able to run. When this fails because of the setup condition we get:

F
======================================================================
FAIL: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Projects\Experiments\test2.py", line 21, in setUp
    self.assertIsNotNone(self.content, "Failed to load test data")
AssertionError: unexpectedly None : Failed to load test data

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

Solution

  • The purpose of setUp is to reduce Boilerplate code which creates between the tests in the test class during the Arrange phase.

    In the Arrange phase you: setup everything needed for the running the tested code. This includes any initialization of dependencies, mocks and data needed for the test to run.

    Based on the above paragraphs you should not assert anything in your setUp method.

    So as mentioned earlier; If you can't create the test precondition then your test is broken. To avoid situations like this Roy Osherove wrote a great book called: The Art Of Unit Testing ( For a fully disclosure Lior Friedman(He was Roy's boss) is a friend of mine and I worked closely with them for more then 2 years, so I am little bit biased...)

    Basically there are only a few reasons to have an interaction with external resources during the Arrange phase(or with things which may cause an exception), most of them(if not all of them) are related in integration tests.

    Back to your example; There is a pattern to structure the tests where you need to load an external resource(for all/most of them). Just a side note; before you decide to apply this pattern make sure that you can't has this content as a static resource in your UT's class, if other test classes need to use this resource extract this resource into a module.

    The following pattern decrease the possibility for failure, since you have less calls to the external resource:

    class TestClass(unittest.TestCase):
    
        def setUpClass(self):
            # since external resources such as other servers can provide a bad content
            # you can verify that the content is valid
            # then prevent from the tests to run  
            # however, in most cases you shouldn't.
            self.externalResourceContent = loadContentFromExternalResource()
    
    
        def setUp(self):
            self.content = self.copyContentForTest()
    

    Pros:

    1. less chances to failure
    2. prevent inconsistency behavior (1. something/one has edited the external resource. 2. you failed to load the external resource in some of your tests)
    3. faster execution

    Cons:

    1. the code is more complex