Testing in Python
Writing Good Unit Tests in Python with Ease
Part 1: Why you should write unit tests
This article series is aimed at people working in the public sector (like me), academics and students, people new to their careers in the private sector and anyone else for whom testing code is not yet second nature. When I refer to “tests” or “testing” in this article the context is constrained to unit and component/modular tests. My experience is mostly derived from building analytical pipelines in Python using pandas
and pyspark
and testing in pytest
.
The series started as a written summary of a presentation given to the Office for National Statistics on Feb 26 2021. There is also an accompanying GitHub repo with some more advanced test parameterisation examples and other resources.
Here is a list of the articles in this series:
- Part 1: Why you should write unit tests(this article)
- Part 2: The basics of testing with pytest
- Part 3: Testing workflow tips
Skip to a section of this article:
Series Introduction
Testing can often be something that some developers can be resistant to get on with, especially when starting out. About a year and a half ago, not long after the start of my time writing in Python, I knew that having no tests was a gaping hole in the quality of my code but the barrier to entry always seemed too great — particularly when actually learning the language itself required so much of my learning effort.
Fast forward to now, my testing muscles are fairly well developed and writing tests has gone from being a burdensome chore (one I’d often just skip entirely), to something that I actually, dare I say it… enjoy? Perhaps “enjoy” is a bit much, but there is definitely a contentment and satisfaction that comes with a passing test suite, and as projects grow you too will consider your tests invaluable.
One beautiful side effect of writing tests is that a deep appreciation will blossom for your past self who saved your future self tons of pain and frustration. Be kind to your future self, write tests!
Some of my favourite views


The aim these articles is to impart some of my limited wisdom on testing and also to provide some motivation as to why you should start writing tests if you’re not already doing so. I also want to touch on a few techniques, tips and tricks to help you create beautiful test scripts that are a delight to read and to also bring a bit of fun to the process.
I’m hoping for these to be the articles I wish I had when I was starting out and to also offer some interesting insights for seasoned testers. As always, you — the reader — will be the judge of that, and I look forward to reading your feedback, disagreements and discussion in the comments section.
Testing is time 𝖼̶𝗈̶𝗇̶𝗌̶𝗎̶𝗆̶𝗂̶𝗇̶𝗀̶
Testing is time well spent
You should be spending as much time testing, if not more, as you do building features.
How does that statement above sit with you? If you think that being expected to spend more time testing than you do building is absolutely ludicrous, when you’re already time-pressed to deliver on your specification — you are not alone! I used to feel exactly the same. Testing was something that I thought was going to slow me right down and that I’d never get anything done in a reasonable time.
But now, through first hand experience, I’ve come to understand just how important testing is. My life is so much easier down the line if I take the time upfront to put some good tests in place. Now, I always make time for testing. And you should too.
Testing is important and you should be making that clear to whoever you’re delivering the work for. If you are costing up a product or feature build then make sure time for testing is included as part of the work package. If you are finding yourself squeezed on time or resource when it comes to testing, you probably didn’t set the right expectations. Set expectations with whoever you’re delivering the work for (even if that’s yourself!), and set them early.
If you are tempted to just carry on in the same way — you’ve built your feature, you’ve done some exploratory testing, you can see that it seems to be working for your inputs — let me just make it clear once again that time saved by not writing tests is a false economy. You will spend much more time dealing with bugs in the future than you spent writing the tests. And if not you, it will be someone else that has to clear up your mess.
One bad programmer can easily create two new jobs a year.
David Parnas
If you want to be taken seriously, and you want your code to be taken seriously, then you need to have tests. If you want to have tests, then you need to make time to test. Code that is untested is incomplete. Make time to test, it will never be time wasted.
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
Martin Golding
Testing is a skill
Remember that there are people out there with the job title “Tester”. Learning how to test is not easy, so don’t beat yourself up if you hit a few stumbling blocks. It will take time to develop those testing muscles, but it’s a skill worth investing in.
The number one most important thing that you need to do to get better at testing is to write some tests! There really is no substitute. It may be a painful lumbering process at first, but you will begin to stumble on things that will improve your workflow. You will have a set of different test structures for different situations that you lean on, you will develop useful snippets you can cut and paste… Take the time to figure out what works and what doesn’t. Write a test, get it to pass. Write another one. It’s the only way.
The next best thing to writing is reading:
- Read the documentation for your choice of testing framework.
- Read some guides/walkthroughs on how to get started with testing.
- Read some tests in a library that you like, or by a mentor or more experienced colleague.
Testing will make you a better programmer
There’s no doubt about it, my programming has massively improved since I started writing tests regularly. If I code while holding the thought “How am I going to be able to test this easily?” it means that I arrive much quicker at functions that are, surprise surprise, easy to test.
Functions should really only do one thing and one thing only: I like to think of them as executing a single unit of logic. Start with the simplest functions that will lay the foundation for your program — they should be clear, concise and named well. Test each one. You can then began to layer these units of logic within other functions — a function that combines other functions to create more complex behaviour is often called a component or a module. Control the flow of execution by combining components or modules in another function.
Layering behaviour like this can result in computer programs with many complex interfaces. Having a suite of tests at the lowest levels of logic will result in a more stable program, and when things do go wrong, more effective issue triage and bug squashing. If you were building a tower block you wouldn’t use steel beams that hadn’t been stress tested. Similarly, your programs shouldn’t use critical components that haven’t been effectively tested.
Congruence between the function name and the code being executed is important. If a line of code is executing a task that doesn’t fit within the unit of logic described by the function name, either the function needs a new name or that code doesn’t belong there. Your function names should read like steps in a cookbook recipe — “Do this”, “Get this”, “Combine this” — imperative commands that would make sense to a human. Python is very good for expressing things in this way.
Once you’ve configured your logic into watertight units, designing test data becomes a breeze. Rather than trying to run through multiple logical steps on your test input to get an expected output, you just need to apply one step and you’re done. One quick glance at the docstring, test input and expected output and it should be crystal clear what that function is doing. If it’s not, the code could probably do with a refactor. What is a refactor? It’s a restructuring of existing code, without changing the overall behaviour, to either improve readability, reduce complexity, or both. Having tests in place before refactoring means you can easily prove that the overall behaviour has not changed.
Building, testing and refactoring should be a constant cycle — the holy trinity of programming. The first version of anything is usually a long way off it’s best. If you had to write a book or a report you wouldn’t hand in your first draft, so why do some people seem to think this is OK when it comes to your code? Writing tests are a good tool for you to self-edit your code and get you closer to a finished product.
Write some code, write some tests. Improve your code, improve your tests. And so on…

Testing is documentation
What do the Sistine Chapel, Monet’s waterlilies and your test script have in common? Well they are all a pleasure to look at of course. At least your test script should be a pleasure to look at… While of course utility is the most important attribute of your test suite, (your tests should be well designed, have good coverage and should pass), taking some care to layout your tests in a way that is easy to read and understand elevates your test script into something more.
Readability counts
Zen of Python
Your test script should take the reader through a clear cut example of how a function or class is supposed to be used, and often, how it’s not supposed to be used, without having the need to run it themselves. For me, I want to see what’s going in, what’s being executed, and what’s coming out. Anything else is a distraction. If you have complex setup procedures, abstract as much as you can out of the test script so that you’re left only with the bare essentials for understanding what your functions are doing. Make good use of pytest
fixtures and your conftest.py
, as well as additional test helper functions which can live either in a helpers module or also in your conftest.py
depending on preference.
Treat your tests as a living piece of documentation for your code — because that’s what they are. Write them with the expectation that they’ll be read months and years down the line — because they probably will be. Think about structure. Think about keeping your test data as close as possible to your test. Think about anything and everything that’s going to make it easier for a reader to see what is going on and you will arrive at a tight piece of documentation you will keep returning to.
Wrap Up
>>> Continue to: Part 2 — The basics of testing with pytest
- OK — now you’ve read the article, go write some tests.
- Check out the accompanying GitHub repo for some additional resources.
- If you liked something about this article or found it interesting, please leave a “clap”.
- If you disagreed with something or have something else to share, then please leave a comment.
I hope these articles will be a good reference for you on your testing journey. Bon chance!
More content at plainenglish.io