Skip to main content

Unit Tests

tip

Run through the steps in Setting Up a Dev Environment prior to modifying/testing code.

Running Unit Tests and Coverage Checks

  1. Navigate to the task's base folder.

  2. Activate the virtual environment.

  3. Run

    coverage run --source [name of lambda] -m pytest
  4. Output the coverage results to the file system by running

    coverage html
tip

For error-free running of postgres tests, see Postgres Tests.

Writing Unit Tests

Any written code should have a minimum of 80% coverage, with higher coverage ideal. This is a requirement for any new code, and will apply retroactively to old code as we have time to create/update tests.

As described above, we use pytest for running unit tests. If pytest reports new or existing tests failing, then this must be resolved before a PR can be completed.

Familiarize yourself with Mock and Patch as they are vital for testing components in isolation.

Unit Test Standards and Tips

  • Title your testing class with the format

    class Test[class name](unittest.TestCase):
  • Test a single piece of functionality at a time, such as a single path through a function. This will make tests more valuable as diagnostic tools.

  • Title tests with the format

    def test_[function name]_[conditions]_[expected result](self):
  • Avoid using assignments to mock functions and objects.

    class.func = Mock() # This is dangerous

    These Mocks will persist between tests, potentially causing failures at best, and false-positives at worst.

  • Create mocks using patch

    @patch('class.first_func')
    @patch('class.second_func')
    def test_name(self,
    second_func_mock: MagicMock,
    first_func_mock: MagicMock):
    note

    Decorators reverse in order when passed to parameters.

    tip

    You can assign Mocks to Mock properties without your Mocks persisting between tests. These Mocks will persist for the duration of the test, then will be removed.

    func_mock = Mock()
    class_mock.func = func_mock # This is fine
  • Tests should assert any effects that go outside the test's scope. Depending on the size of your test, this could be

    • Calls to external classes
    • Calls within the class to different functions
  • Tests should assert that affects you DO NOT expect do not occur. For example, if 2/3 values in an array are passed through to another function then your test should assert that only the two values in question were passed. Similarly, if the conditions in your test bypass an external effect, Mock that effect and make sure it is not called.

  • Generally speaking, any Mock you create should have at least one assert statement. The main exception is logging messages, particularly verbose or debug messages.

  • A different group of asserts are used to check raw values, such as the return value of the function under test.

    self.assertEqual(expected_result, result, "Message to be displayed when assert fails.")