Keep Calm and Add Unit Tests with Python

“I just can’t figure out why my program doesn’t work. Can someone help me?”

“I’ve been working on this for hours and I’m still stuck.”

“I had it working a few minutes ago, but I did something and now nothing works!”

Making Your Own Private Hell

You probably mastered a few basics, like writing a simple Python program. You’ve gained confidence that you can write a loop or a function. You can see how a basic Python program works. You are hungry for more.

Maybe you are plowing through a book or a course on Python. Maybe it’s a class you’re taking. Or maybe you are getting ambitious and you decide to create something new, like a Reddit bot.

You are busy adding working code. You test every now and then by running the program. If there’s a problem, you edit the file and run the program again. Everything seems so easy.

Then you realize something is wrong. That part you tested 10 minutes ago, by hand, but didn’t test until just now… it’s not working. Are you just imagining this? No, you try it again. It’s still wrong. It’s still broken. If you run the program 10 more times, magic will happen and it will work. Right?

Again and again you try to fix the problem, but it only ends up somehow worse than before. Minutes turn into hours. Time is slipping by. You are so frustrated you are ready to hurl your computer out the nearest window. Why did you think you could do this programming thing anyway? It’s impossible! Only computer geniuses could keep all this straight. And this is just a basic program!

“Is it too late to get my money back?”, you think.

The Way Out

What if you could avoid all of this misery? Imagine if you could use Python itself to check if your new program was correct every time you made a change? As soon as you made any change, you could check to see if you made a mistake. You could find out right away if the change you just made made things better or worse?

Think about this: if you can check your program after every change, and you keep the changes small, then you only have to undo a small change to get back on track. This would keep you from wasting your time with big changes that will derail you for hours or days.

You should write automated software tests. When I say “automated” I mean tests you don’t have to keep in your meat brain. You just have to remember to run a single command to run every test instead of remembering to run a program multiple times to check every situation.

There’s another important benefit to these tests – it’s a professional habit. Writing tests differentiates the pros from the slobs. Writing tests for your program is like basic sanitation for doctors. Would you trust a doctor to perform surgery without washing their hands first?

Writing Tests – A Detailed Example

In this example, we already have a Python source file called

For now, I just want you to get comfortable with the tools of creating and then running your first real tests. That’s what you’ll learn today.

You’ll get the most out of this example by following along and running the examples. The original program is located here. If you are familiar with git, you can clone the repository. Otherwise you can just copy the source code locally.

First let’s look at files in the original project.

The is theĀ implementation and is a test driver.

The test driver in this project is a “poor man’s” test framework. It’s functional, sure. If you run the file you’ll get a result like this:

We’re going to use the to make our first tests easy to write. You only have to concern yourself with how to construct the tests using a testing framework. We’ll use pytest for this example.

Let’s run pytest and see what happens:

Just as we expected, no test were run. Let’s add the file that will hold the tests. Let’s call the test file The name is important.

As mentioned in the pytest documentation, the framework uses standard test discovery to find and run tests.

We’re concerned with 2 basic rules in this example:

  1. files named test_*.py in the directory
  2. files named * in the directory

Let’s run the pytest command again with our new (empty) test file,

Again, no tests were detected, so no tests ran. Let’s add a test.

If we use the existing smoke test file as a guide, you’ll see a series of tests lists. As a general rule, it’s easier to test the behavior of code that has the fewest dependencies. What do I mean by this?

Looking at the Rational class, you might be tempted to start testing there. However, you’ll see that the implementation of that class depends on another function, gcd.

If a test of the Rational class fails we can’t be sure that the class is responsible for the error. At least not yet. We should drill further into the dependency of the class and start with the “bricks” the foundation of the Rational class is built upon.

This is why gcd is a good candidate. The comments in the function tell us that it’s based on Euclid’s algorithm for finding the greatest common denominator (gcd) of 2 integers.

Since we know the general expectations of how gcd works, we can start with a simple test case. On paper, Euclid’s algorithm predicts the gcd(3,0) will be 3.

Our test file now looks like this:

import rational

def test_gcd_a_0_is_0():
assert True

If we run pytest again:

So far, so good. Let’s replace the assertion with a real test, but with a clearly wrong answer.

import rational

def test_gcd_a_0_is_0():
assert rational.gcd(3,0) == 100000

You might wonder why would add a test that is going to fail. We want to verify that the test we are creating is going to run and that if there is an error in the test we’ll catch it.

Consider how blind we would be if we created a faulty test and the test framework ignored the failing test. How much time would we waste building on this false assumption? It’s better to check now, at the start, and catch stupid mistakes before they cost us an arm and a leg.

Isn’t that great? pytest tells you exactly where it fails and why. Let’s repair the test with the correct expected value.

import rational

def test_gcd_a_0_is_0():
assert rational.gcd(3,0) == 3

Then verify that with pytest:

That’s it, you did it! You created a test and know you know how to build more. Instead of trying to remember each test case and run them manually, you only have to run one command to run all the tests.

That feeling you have right now? That’s the feeling of victory over chaos.

Yes, You Should Add Unit Tests To Your Project

Should I go back on what I have in my project and write some tests for it, or just finish the project?

You’ve worked long and hard on your projects only to realize that you didn’t write any tests. You’ve read how important tests are. You probably also know this is a best practice, but it’s so much more work. Even worse, what if the tests show you that there are problems. Then you’ll have to work even more to fix them!

The truth is your project isn’t done. Every software product has bugs – you just haven’t found them yet. Of course, you want to do a good job. You want to show people you know the basics. You want people to know you are ready for the next step. But you know, deep down, the whole house of cards will collapse.

You’ll be exposed as a fraud who didn’t bother to test your work. No employer will touch you. You’ll end up living in cardboard box, down by the river, subsisting on a diet of government cheese.

You want tests. You want a lot of tests that give you confidence that when you make a change you’re not breaking things. You want to run your tests whenever you like. You want continuous integration to run those tests whenever you commit a change. You want the world to see you understand how to make safe changes.

The fix: write ONE test first.

You don’t have to blanket the project with tests all at once. In fact, if you try that, you’ll fail. You’ll lose motivation when you realize just how many tests you should create.

You need to create a momentum of success first. You need to have a small, solid win. From there you’ll pick up steam and get more tests in place.

  1. Find your unit test framework and install it.
  2. Find the part of your project that is the easiest to test.
    • Look for code where you have little or no dependencies. Hint: If it touches a network or file system, keep looking.
    • Find functionality where you can isolate a function or class method. Hint: If it takes setting up helper objects then keep digging into the helper objects.
  3. Create a test file anywhere. Don’t worry about the “right” place to put it.
  4. Run the test. Yes, it will fail.
  5. Write one test in a way that you know it will fail. For example, if you are testing a method that should return a number, have the test expect a string.
  6. Fix the test so it passes. Hint: this might take more than one try. That’s fine!
  7. When the test passes, commit that test.
  8. Take a break. Enjoy this feeling!

That’s it. That’s how you get started. Next step is to keep adding tests, one at a time.

If you want to see a detailed example of adding tests, check out

Get the next post right to your inbox!

Did you find this useful? Sign up and get more tips and help!