I’ve recently been able to sit down and do some more backend work on some SondeHub backend code. Some of these changes I wanted to ensure didn’t cause any problems or issues to end users. To help build confidence that the changes would ok I started looking at Python unit testing solutions. I’ve only done minimal unit testing in Python before some time ago, but with a quick read of the unittest python docs page I was up and running.

I’m not going to bore you with the details here, but I wanted to share some of the features I found quite useful to test some bits of our code.

The first is MagicMock(). This lets you quickly mock out a method. The advantage to using MagicMock over your own method is that you can quickly assert a bunch of conditions to check if the method was or wasn’t called and with the correct arguments. An example usage of this might be:

import boto3
from unittest.mock import MagicMock

sns = boto3.client("sns",region_name="us-east-1")

# Mock sns.publish
sns.publish = MagicMock(return_value={"meow":"meow"})

sns.publish(TopicArn="blah", Message="blah")

# check to see if it was called
sns.publish.assert_called()

The mock library also contains patch which works very similar to MagicMock, but you can use it as decorator. When used this way you don’t need to worry about cleaning up or resetting the mock for every test. You can also use the wraps argument to call a real method while still retaining the ability to trigger assertions.

@patch('time.sleep', wraps=time.sleep)
def test(PatchedTime):
    time.sleep(10)
    PatchedTime.assert_called()
    PatchedTime.assert_called_with(2)

test()

Speaking of assertions, it’s sometimes handy to test that methods are called with the correct arguments in the correct order. Python unittest makes this fairly easy allowing a list of calls to be checked using assert_has_calls

from unittest.mock import MagicMock, call, patch

es.request.assert_has_calls(
    [
        call(json.dumps({}),"flight-doc/_search", "POST"),
        call(json.dumps({}), "ham-telm-*/_search", "GET"),
        call(json.dumps({}),f"ham-predictions-{date_prefix}/_bulk","POST")
    ]
)

With unittest I was able to create a few core tests for functionality along with write tests for the new functionality I was building (yay test driven development!). Even with the patchy cell coverage along my train ride was able to quickly iterate over a few designs without the need for manual testing against a live backend. I know that this hasn’t been an in depth look into Python’s unittest functionality, however I hope it sparks someones interest in giving it a go.