How to Build a Testing Library Like Pytest?
As Pythonistas, we all love Pytest. No questions there. But what if we embark on a journey of building something similar on our own?
Introduction
Let's talk about a skill that every developer dreams of mastering - writing unit tests. We all know that testing is the secret sauce that makes our code rock-solid and dependable. And when it comes to the Python world, pytest is practically our code-testing deity! 🙌
But wait, have you ever wondered how pytest works under the hood? Sure, you can find a gazillion tutorials on how to use it, but let's take a different route. In this blog, we'll embark on an exciting journey to build our testing library from scratch.
Pytest 101
If you have never seen pytest before, I don't want you to feel left out. So let's start there with an introduction to the Pytest. For this sample the folder structure will look something like this
With contents
Now you install pytest
Run pytest
Now all tests in the tests
folder runs.
Other Pytest Features I love
Parametrized Test Cases
Fixtures
Fixtures come in handy when you want to set up and teardown some resources before and after a test case. For example, you want to set up a database connection before a test case and teardown after the test case.
Check for exceptions
Why this? Why Now?
Wonderful, now let's take all these features as our inspiration to try and build our own.
Why do this? For fun, because we can :P
What I cannot build, I do not understand - Richard Feynman
It's fun. I told you that already.
Can't Copilot do this for me?
Trust me, I've tried it.
It didn't perform well since there were no code examples for the copilot to learn from. But when I tried specific cases like Fixtures, the results were good for me to draw inspiration from.
Disclaimer: This is an experiment never meant to see production or usage. The code doesn't resemble pytest. Infact, all my attempts to read the pytest code went down the drain. This is purely my version
Features of a Testing Library
Test Runner
Find all test files and test functions. If the folders or files don't start with
test
, we skip it
calling the test functions.
Once we have the test function object, let's call it and capture errors if any
Capture all test results (success, failure, error)
Parametrized test cases
If you look at parametrized test case of Pytest, you'll notice that it's driven by decorator. Let's start there
create
owntest.parametrized
function. Since the parametrize function takes values on setup, the decorator will be 3 layered.
Let's preserve the parameters. On decorating, let's format the values and make it easy for consumption in test cases.
Run parametrized test cases, with each parameter.
During test run we loop through each parameter and call the test on each parameter individually
Fixtures
Let's create owntest.fixture
function. This decorator will store the fixture function in a dictionary. Later when we run the test, we can map the function attribute names to a fixture.
Register Function as Fixture
Create a map of Fixtures
Update the test runner to maintain a mapping of fixture when it encounters one.
Handling Generator Fixture
If the recognized function is a fixture, as stored in fixture mapping, check if the function is a generator. If yes, generate the value with __next__
else use the return value directly on the test function
The Wrap-up
And that wraps up our adventure into the world of building a testing library in Python! We've covered all the essential components - test discovery, functional test cases, fixtures, and parametrized test cases - that come together to create a fantastic testing framework.
Now armed with this newfound knowledge, you have the tools to craft your very own testing library customized to suit your project's needs. It's an exciting journey that lets you showcase your coding prowess while ensuring your code is thoroughly validated.
So, go ahead and take on the challenge and rebuild ownpytest in your own way. Maybe think about testing your testing library.
Until my next freaky experiment, see ya.
I will be presenting this post as a talk at Pycon AU 2023. If you are around, say hi.
Last updated