Python Unit Testing

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.


Melbourne (Half) Marathon 2023

Yesterday I took part in the Melbourne Marathon Festival Half Marathon. While this was my first half marathon race, this wasn’t the first time I had competed in the festival having completed the 5km event last year in 30 minutes.

Alex and I running in front of Flinders St station

When people asked if I would be entering long racers or doing longer runs I shrugged it off as it would take significantly long to train for. I started the year off training for the 5km event again.

It probably sounds a bit silly, but my original goal was for a sub 25 minute 5km. The reasoning behind this is when you sign up for the Melbourne Marathon Festival each event has two different category. For the 5km this was below 25 minutes and above 25 minutes (inverted colours). I wanted to enter for the faster speed for the event. This would require shaving 5+ minutes off my personal best.

Goal: 5km sub 25 min

With the longer and longer training runs, and completing a few half marathons for fun, I realised that I might be in with a chance for entering the festival’s half marathon in the “faster” category @ 2:10:00 - so I did just that.

Goal: 5km sub 25 min 21.1km sub 2:10:00

Then in June I broke 2:06:18 during training, followed by 2:03:46 in July. Maybe I could do even better than I expected. Could I complete a sub 2:00:00 half?

Goal: 5km sub 25 min 21.1km sub 2:10:00 21.1km sub 2:00:00

Lead up

I ended up running about 30-35km a week, along with a bit of cycling. Tapering off about 2 weeks before. I think the distance could have been longer but its what I could fit in for time and recovery. Roughly every month or two I was also doing a half marathon.

Most of my training was less about getting my cardiovascular or leggies strong enough but actually getting my back used to the distance. While I’ve found 5 and 10km runs fine, 21.1km pushes the limit of my scoliosis.

The next problem I had was shoes. This turned into quite a conundrum for me. For short runs I had been using NB 1080v12 - these were soft and nice to run in but on long runs I started getting blisters. I switched to using my NB Hierro v7 - these I found a little more firm and grippy. However the Hierro v7’s were already somewhat past when they should be replaced, and were only going to get worse. They contained no grip, and barely any padding.

Hierro shoe with back and front grips worn down flat
Trail shoes should not look like this

This lead me to go buy new shoes. I was tempted to buy another pair of Hierro v7’s, however they aren’t really the best shoes for this kind of run. I ended up purchasing a pair of HOKA Clifton 9. Initial short runs these were amazing, and set some really good times with these shoes. But I quickly discovered on long runs I got large blisters.

I was once again stuck. It was too close to race day to try more shoes, so in the end I went with the NB 1080s. Some more practice runs showed that wearing better socks, the extra wear-in, and better form made the blister problem negligible.

Unfortunately after some of my tapering period some of my runs left me with some pain and issues in my knees and ankle areas. A 2 day rest before the event seemingly sorted that out - maybe or maybe not. On the good news my HRV status had moved from bad to great over the last week.

The day

I’m not sure if it was eating in the morning or the anxiety of running a half - but my stomach was not happy. Walking to the start feeling like I was going to puke the entire way. I guess for me it was lucky that the start chaos meant a fairly slow start to the race and my stomach was able to settle in the first kilometre.

Alex had an unfortunate series of events causing her not to be able to run the pace she had planned, but luckily for me she was able to be my own personal pacer. We placed our selves just behind the official 2:00:00 pacers, but due to the slow start I lost sight of them very quickly.

I’m really glad to have had Alex with me. I didn’t quite understand the sheer number of people who I would be running with and often got overwhelmed. Having her there with me was able to keep me calm. There were around 10,000 people in the half marathon - though the course contains sections that overlap with the full marathon, adding to the amount of people.

Around the 5km mark I saw the pacers in the distance and by around the 10km mark we weren’t far away from them. We were setting around 5:30min laps - a great pace and better than I thought.

My energy/food plan was to start eating a chocolate bar around the 8km mark and eat a little bit more of it over the next 4 or so km. I had done this on previous runs and it had worked well. This plan did not go so well however. At the 7.5km mark I tried to start eating only to find that I had managed to melt the entire chocolate bar into goo. I had a little bit of it but didn’t eat any more of it after that. Given I had been calorie negative for the last few weeks, this was not good.

Alex and I running around a corner on the road
32047 did a respectful 02:00:18 and a much more consistent pace than myself

The pace started to slow and at the 15km mark we were doing 6:00min/km. I could no longer see the 2:00:00 pacers. I felt completely out of energy along with some ankle and hip pain. At 17km I walked for a short amount, and again at 19km. The desire to push through was high but I’m glad I didn’t - seeing a number of people collapsed near the finish line made me remember that it’s not worth putting yourself at that risk.

Alex and I approaching the finishing line

I was definitely at my limit entering the MCG, barely making it through the entrance, but seeing the line gave me just that little bit of extra energy to get me to the end. Alex encouraging me to pick up the pace - though I didn’t really have much left in me. We crossed the line together holding hands. An amazing event.

Alex and I crossing the finish line holding hands

Official time of 2:02:06. Not quite the planned sub 2 hour, but still a personal best and an amazing achievement for someone who had only really just completed C25K in 2022.

Selfie of Alex and myself with finishing medals post run

I’m not entirely sure what’s next - but I have something special for myself planned for December. One thought I’ve had is trying for quicker Parkruns. I know next year I’m certainly going to try and work out the pre food and race fuel stuff a bit better - my stomach is still recovering.


Android apps I've started using. Vol 1.

Here’s some apps I’ve recently started using and enjoying. They might be of interested to you.

DITMM ( Did I Take My Meds? )

Screenshot of Did I Take My Meds?

I used to use Google Calendar reminders to remind me to take medication. This was less than ideal because it didn’t keep a log of if I took the medication, along with I wanted to close out my Google Calendar account.

DITMM fills my needs by showing a notification when I need to take my medication, along with tracking when I last took them. It can also record some basic information like prescription number (Rx #), dose amounts and other metadata.

An interesting side effect of using a medication tracking app is you can use the data to generate plots for how well you are at taking medication on time. I wrote some python code to generate the below plot for myself.

Chart showing time difference from scheduled to actual medication consumption

Etar

Screenshot of Etar Calendar app

Etar is an opensource calendar. It has pretty much everything you expect out of a calendar app. I started using it as I wanted to reduce my dependency on Google Apps and what brought me to it was that a forked version is provided in LineageOS. I use it in combination with DAVx⁵ to sync with Fastmail. It also works with Google Calendar/Workspace so I only need one calendar app to cover both needs.

K-9 Mail

Screenshot of K-9 Mail compose screen

Fastmail is great, but their app does not work in offline mode. I spend a lot of time outside of phone coverage and this drives me crazy. I was using Nine mail but switched to K-9 Mail because Nine is really more focused on Exchange users and K-9 is opensource.

There’s nothing fancy about K-9 Mail, it’s a no frills mail client. It receives email. You can have unified or seperate inboxes for accounts.

I’ve also configured a Google Workspace account through IMAP using the device passwords feature - works well enough for me and means I can remove the GMail app from my phone.

SuperCycle

Screenshot of SuperCycle app home screen

A departure from the above apps, SuperCycle isn’t opensource - however unlike other cycling apps on the Play store it doesn’t contain ads, paid features and is entirely free.

Screenshot of SuperCycle viewing a historic ride

SuperCycle is a bike computer for your phone. You can pair it to bluetooth speed, cadence, power meters and heart monitors. I use it so that I can monitor my heart rate and cadence while riding. Rides can be recorded and uploaded to Strava. It supports various maps and you can load GPX routes into them for navigation.