Learn

Python unit testing: An introductory guide

Author:

Tricentis Staff

Various contributors

Date: Mar. 18, 2024

Over my career, I’ve met several developers who were intimidated by unit testing. I used to be one myself. Once I had a chance to learn a little bit about unit testing, my intimidation went away. I’ve been fortunate enough to share what I know about unit testing with many of those developers.

Today, we’re going to talk about unit testing, and specifically Python unit testing. By the time we’re done with this post, you’ll find that there isn’t any reason to be intimidated by unit testing.

The people I’ve met who are intimidated by unit testing usually fall into one of three camps: they’re not sure what unit testing is, they’re not sure how to effectively get started, or they’re not sure how to do it the “right” way. We’ll talk about all three of those things in this post.

python unit testing

What is Python unit testing?

Unit testing is a technique by which developers write small tests that they can run automatically to verify that small pieces of code work. We call these small pieces of code “units.” Each individual test runs to test a single unit of code.

How small should a unit be? Generally, as small as possible. When I’m writing unit tests, I write a test for every branch of an if statement within a function. The idea is that you have one full test for each possible unit of behavior in your code.

How small should a unit be? Generally, as small as possible.

How does a unit test work?

Whether you’re writing for Python or any other language, you’re likely to structure each test similarly. Let’s go over the major steps of a unit test:

  • Establish the context. This might take the shape of inserting rows into your testing database or mocking function behavior. The goal of a unit test is to test one specific unit of behavior. When you establish the context, you basically say, “This is how I expect the state of the world to be.” Each unit test is testing only one unit of behavior. This differentiates it from a functional test or an end-to-end test. So, step one is to establish the expected state of the world.
  • Define your input data. Step two of a unit test is to define the data you’ll send to the function you’re testing. The benefit of a unit test is that it directly calls the function you’re testing. This means that you need to pass a value for each valid parameter to the function when you test it. For quite simple function parameters, like strings or integers, you might simply call the function with the parameters defined in line. For more complicated parameters, like full objects, you’ll probably want to define them on separate lines to keep your tests readable.
  • Define the expected result. This is just like defining your input data, except here, we’re defining what we expect the output to be. Just like with inputs, we might be OK defining the expected output directly in line with the assertion statement. For more complicated objects, you might want to define them before you test the output.
  • Assert that the function result matches what you expect. This is the last step of the unit test. You call the function and store the result in a variable. Then, you use the unit testing library’s assertion capability to assert that the function result equals what you expected. If it does, then when you run the test, the test will pass. If it doesn’t, or if your code throws an error, the test will fail.

What does a unit test look like?

Let’s steal a couple examples from the Python unittest docs:

import unittest

class TestStringMethods(unittest.TestCase):

def test_upper(self):

self.assertEqual(‘foo’.upper(), ‘FOO’)

def test_isupper(self):

self.assertTrue(‘FOO’.isupper())

self.assertFalse(‘Foo’.isupper())

def test_split(self):

s = ‘hello world’

self.assertEqual(s.split(), [‘hello’, ‘world’])

# check that s.split fails when the separator is not a string

with self.assertRaises(TypeError):

s.split(2)

These tests are each fairly simple, but they illustrate the process nicely. None needs to pre-define any context, and they show how we can define both our inputs and our expected outputs directly in line.

It’s important to remember that each unit test is a normal function. Anything you can do in normal code you can also do in a unit test!

What Python unit testing libraries should I use?

There are a variety of popular Python unit testing frameworks. All of them allow you to write useful tests quickly and run those tests automatically.

Let’s break down the most popular frameworks and talk about why you might use each one.

  • unittest—This is the built-in Python unit testing framework, and it will work out of the box without any additional packages. It’s very well-documented because it’s part of the standard library. There’s a lot to be said for sane, well-documented defaults!
  • Nose2—The developers of this package wrote it to improve the process of testing code with unittest. Nose2’s libraries offer functions you can leverage to reduce repetition within your unit testing code. To get the full picture, you should visit the docs, but the primary highlight is probably the such domain-specific language that helps make your tests more readable.
  • TestifyThe Testify library is a wrapper around both unittest and Nose. Much like Nose2, the goal is to provide developers helpers that reduce the amount of code they need to write for each test.
  • PyTest—Unlike the other libraries on this list, PyTest is not based on unittest. It is an entirely separate unit testing library. If you’re working in an existing code base, there’s a good chance that you have at least a few PyTest tests. PyTest has wide adoption because the syntax that it uses for tests often feels more “pythonic” like the rest of the python in your code base.

Each of these major libraries does the same basic things. Each test you would write uses the same structure outlined above. If you’re trying to figure out which to use for your project, test them all out. See which one fits your needs and which one you like writing the most. The best unit tests are the ones you write.

Python unit testing best practices

To wrap up, let’s talk about a few best practices for unit testing in any language, and specifically for Python:

  • Actually write tests. This is the most important best practice! No matter whether you have one test or 1,000, a good unit test adds value to your code base.
  • Avoid the biggest testing mistakes. Take a gander at the biggest software testing mistakes you want to avoid.
  • Test the smallest units you can. It’s easy to try to do too much in each of your tests. This makes the tests more complicated and difficult to read or reason about, so try to make sure that each test tests only one thing.
  • Automate running your tests. One of the big benefits of unit testing is running those tests on every commit. That ensures that the commit’s changes didn’t upset any applecarts within your existing code. At Tricentis, we know a lot about test automation, and we can help you figure out how to automate your tests.

Don’t be intimidated by unit testing

Like we talked about at the start, many developers feel intimidated by unit tests. They’re afraid of doing them the “wrong way.” But as we’ve noted, the worst way to write unit tests is not to write them at all. Each unit test you write adds value to your code base. Each unit test helps document your code and ensure that future changes don’t break your existing logic.

You don’t need to make sure that every single test is perfect. And if you’re just getting started, it might take you a little while to get comfortable. But just like the rest of your code, you can refactor tests to make them better in the future, too.

The best day to get started writing unit tests is today.

This post was written by Eric Boersma. Eric is a software developer and development manager who’s done everything from IT security in pharmaceuticals to writing intelligence software for the US government to building international development teams for non-profits. He loves to talk about the things he’s learned along the way, and he enjoys listening to and learning from others as well.

Author:

Tricentis Staff

Various contributors

Date: Mar. 18, 2024