9 Things I Wish I Knew Earlier About Unit Testing In Python
1) Your PR will probably get rejected if you don’t write unit tests
Gone were the days in school where we could just write our code and ship stuff.
When writing production-grade code in the workplace, unit testing is absolutely required. If we leave out unit testing for whatever reason, chances are that you’ll get a comment: “unit tests where??”
The advantages of automated unit testing:
manual testing becomes terribly unscalable as our application becomes large
it allows for regression testing — to ensure that new changes don’t break old functionality
(almost) all logic written in your code can be tested
Verdict — please remember to write unit tests when you’re writing production-grade code in the workplace.
2) We have a built-in unittest module
The unittest module is part of the Python standard library, which means it is built-in and comes with the standard installation of Python, and we don’t need to install it specially.
Here’s some example code:
And here’s 2 test cases that unit test these functions:
^ the unittest module enables us to have an easy way to quickly write unit tests without dealing with boilerplate code and templates.
And if we run this .py file as per normal we get something like:
3) Methods starting with “test_” are automatically tested in our TestCase
The unittest module is designed this way for simplicity and ease of development.
To create a new test case, we simply need to create a new method with a name starting with “test_”
^ if we run the above code, some methods will run, and some won’t.
By default, only methods with names starting with “test_” will run.
test_add10 and test_add100 will run as it starts with “test_”
no_test won’t run as it doesn’t start with “test_”
This behaviour can be customized, but we usually don’t unless there’s really a good reason to do so.
Quick Pause
I recently wrote a book — 101 Things I Never Knew About Python
Here I’ve compiled 101 cool Python tricks and facts that many (if not most) do not know about. Check it out if you wish to level up your Python knowledge!
Link: https://payhip.com/b/vywcf
4) self.assertEqual over assert
This code works, but isn’t considered good practice:
Instead, when using unittest, we often use the build-in self.assertEqual method to check if 2 values are equal:
This is due to the assert statement being optimized and possibly ignored when we run Python using certain flags.
5) self.assertTrue, self.assertIn, self.assertRegex etc
Aside from the standard self.assertEqual, the unittest module provides us with multiple other built-in methods:
self.assertTrue to check that a value is truthy
self.assertFalse to check that a value is falsy
self.assertIn to check that one value is in another container
self.assertNotIn to check that one value is not in another container
self.assertRegex to check that a string value matches a regular expression
and many more
Some examples:
6) self.assertRaises to check for errors
We can even check that certain exceptions and errors happen in certain situations. Let’s say we have a function that raises a custom error:
We can write test cases that ensure InvalidScoreException is raised when we pass in an invalid score.
^ here, using self.assertRaises(InvalidScoreException) in a context manager (using with):
if InvalidScoreException is raised, the test case passes
if InvalidScoreException is not raised, our test case fails
7) How we typically name test cases
By convention, it’s good practice that the names of our test cases follow this format:
test_<function_name>__<more_info>
Firstly, by convention, our test case name should start with “test_”
Secondly, after “test_” , the name of the function we are testing should follow
Thirdly, after the name of the function we are testing, we should have 2 underscores, followed by more information on what this test case is testing for.
To illustrate, let’s write a simple function, then test it. The function:
Some sample unit tests for this function:
8) Mock and AsyncMock objects
The unittest module provides us with ways to easily create mock objects.
Let’s say we have a function that takes in more complex objects:
Here’s how we can create a Mock object to mock this:
The unittest module also provides us with the unittest.mock.AsnycMock object to mock asynchronous functions and coroutines.
9) We can mock API calls using patch()
Sometimes, we need to test functions that make API calls and such. In unit testing, it is often considered terrible practice to actually make API calls — instead, we should aim to mock any API calls in our functions.
Let’s say we have a function that makes an API call:
During unit test, we do not want our function to actually make the API call. As a result, we need to mock this API call, and we can do this using patch:
^ here, we use the patch function to mock the return value of requests.get as we don’t actually want to make an actual call using requests.get
If we want to apply this patch to multiple test cases, we can use patch as a decorator function too.
Conclusion
Hope you learnt something new today!
Cheers