Python's unittest: Writing Unit Tests for Your Code (original) (raw)

The Python standard library ships with a testing framework named unittest, which you can use to write automated tests for your code. The unittest package has an object-oriented approach where test cases derive from a base class, which has several useful methods.

The framework supports many features that will help you write consistent unit tests for your code. These features include test cases, fixtures, test suites, and test discovery capabilities.

In this tutorial, you’ll learn how to:

To get the most out of this tutorial, you should be familiar with some important Python concepts, such as object-oriented programming, inheritance, and assertions. Having a good understanding of code testing is a plus.

Take the Quiz: Test your knowledge with our interactive “Python's unittest: Writing Unit Tests for Your Code” quiz. You’ll receive a score upon completion to help you track your learning progress:


Python's unittest: Writing Unit Tests for Your Code

Interactive Quiz

Python's unittest: Writing Unit Tests for Your Code

In this quiz, you'll test your understanding of Python testing with the unittest framework from the standard library. With this knowledge, you'll be able to create basic tests, execute them, and find bugs before your users do.

Testing Your Python Code

Code testing or software testing is a fundamental part of a modern software development cycle. Through code testing, you can verify that a given software project works as expected and fulfills its requirements. Testing enforces code quality and robustness.

You’ll do code testing during the development stage of an application or project. You’ll write tests that isolate sections of your code and verify its correctness. A well-written battery or suite of tests can also serve as documentation for the project at hand.

You’ll find several different concepts and techniques around testing. Most of them surpass the scope of this tutorial. However, unit testing is an important and relevant concept. A unit test is a test that operates on an individual unit of software. A unit test aims to validate that the tested unit works as designed.

A unit is often a small part of a program that takes a few inputs and produces an output. Functions, methods, and other callables are good examples of units that you’d need to test.

In Python, there are several tools to help you write, organize, run, and automate your unit test. In the Python standard library, you’ll find two of these tools:

  1. doctest
  2. unittest

Python’s doctest module is a lightweight testing framework that provides quick and straightforward test automation. It can read the test cases from your project’s documentation and your code’s docstrings. This framework is shipped with the Python interpreter as part of the batteries-included philosophy.

The unittest package is also a testing framework. However, it provides a more complete solution than doctest. In the following sections, you’ll learn and work with unittest to create suitable unit tests for your Python code.

Getting to Know Python’s unittest

The unittest package provides a unit test framework inspired by JUnit, which is a unit test framework for the Java language. The unittest framework is directly available in the standard library, so you don’t have to install anything to use this tool.

The framework uses an object-oriented approach and supports some essential concepts that facilitate test creation, organization, preparation, and automation:

In the following sections, you’ll dive into using the unittest package to create test cases, suites of tests, fixtures, and, of course, run your tests.

Organizing Your Tests With the TestCase Class

The unittest package defines the TestCase class, which is primarily designed for writing unit tests. To start writing your test cases, you just need to import the class and subclass it. Then, you’ll add methods whose names should begin with test. These methods will test a given unit of code using different inputs and check for the expected results.

Here’s a quick test case that tests the built-in abs() function:

The abs() function takes a number as an argument and returns its absolute value. In this test case, you have three test methods. Each method checks for a specific input and output combination.

To create the test case, you subclass the TestCase class and add three methods. The first method checks whether abs() returns the correct value when you pass a positive number. The second method checks the expected behavior with a negative number. Finally, the third method checks the return value of abs() when you use 0 as an argument.

Note that to check the conditions, you use the .assertEqual() method, which your class inherits from TestCase. More on these types of methods in a moment. For now, you’re nearly ready to write and run your first test case with unittest.

Creating Test Cases

Before you write tests with unittest, you need some code to test. Suppose that you need to get a person’s age, process that information, and display their current life stage. For example, if the person’s age is:

In this situation, you can write a function like the following:

This function should return correct results with different age values. To make sure that the function works correctly, you can write some unittest tests.

Following the pattern from the previous section, you’ll start by subclassing TestCase and add some methods that will help you test different input values and the corresponding results:

In this example, you create a subclass of TestCase with the descriptive name TestCategorizeByAge. Note that the class name starts with Test, which is a widely used convention to make the purpose of the class immediately clear to anyone reading your code.

Also, note that the containing file is called test_age.py. By default, unittest supports test discovery based on the name of test modules. The default naming pattern is test*.py. Here, the asterisk (*) represents any sequence of characters, so starting your modules with test is recommended if you want to take advantage of the default test discovery configuration.

Then, you define six methods. Each method tests for an input value and the expected result. The methods use the .assertEqual() method from the parent class to check whether the function’s output equals the expected value.

Note that the tests above check every possible branch in the categorize_by_age() function. However, they don’t cover the boundary cases where the input age is the lower or upper limit of the interval. To make sure that the function responds as expected in those cases, you can add the following tests:

These test methods have two assertions each. The first assertion checks for the upper limit of the age interval, and the second assertion checks for the lower limit of the next age interval.

Using multiple assertions in a test method helps you reduce boilerplate code. For example, if you use a single assertion to write these tests, then you’ll have to write six test methods instead of just three. Each method will need a unique name, which can be a challenge.

In general, using multiple assertions in your test methods has the following pros:

The approach also has its cons:

With the test cases in place, you’re ready to run them and see whether your categorize_by_age() function works as expected.

Running unittest Tests

Once you’ve written the tests, you need a way to run them. You’ll have at least two standard ways to run tests with unittest:

  1. Make the test module executable
  2. Use the command-line interface of unittest

To make a test module executable in unittest, you can add the following code to the end of the module:

The main() function from unittest allows you to load and run a set of tests. You can also use this function to make the test module executable. Once you’ve added these lines of code, you can run the module as a regular Python script:

This command runs the tests from test_age.py. In the output, every dot represents a passing test. Then, you have a quick summary of the number of run tests and the execution time. All the tests passed, so you get an OK at the end of the output.

Among other arguments, the main() function takes the verbosity one. With this argument, you can tweak the output’s verbosity, which has three possible values:

Go ahead and update the call to main() as in the following snippet:

In the highlighted line, you set the verbosity level to 2. This update makes unittest generate a more detailed output when you run the test module:

This output is more detailed. It shows the tests and their result. At the end, it summarizes the test run as usual.

If you want to make the detailed output more descriptive, then you can add docstrings to your tests like in the following code snippet:

In this update of your test methods, you add human-readable docstrings. Now, when you run the test with a verbosity of 2, you get the following output:

Now, apart from the odd function names, unittest uses the docstrings to improve the output’s readability, which is great.

Skipping Tests

The unittest framework also supports skipping individual test methods and even whole test case classes. Skipping tests allows you to temporarily bypass a test case without permanently removing it from your test suite.

Here are some common situations where you may need to skip tests:

The following decorators will help you with the goal of skipping tests during your test running process:

In these decorators, the reason argument should describe why the test will be skipped. Consider the following toy example that shows how the decorators work:

In this sample test module, you have three test methods. The first one never runs because you use the @skip decorator on it. The second test method only runs if you’re on a Python version equal to or greater than 3.12. Finally, the last test method runs if you’re on a Windows box.

Here’s how the output looks on Windows with Python 3.11:

In this case, the only test that runs is the last one because you’re on Windows. The first test doesn’t run because of the @skip decorator, and the second test doesn’t run because the Python version is less than 3.12.

Here’s the output of these test in macOS or Linux with Python 3.12:

In this test run, the first test doesn’t run. The second test runs because the Python version is the expected. The final test doesn’t run because the current platform isn’t Windows.

Creating Subtests

The unittest framework allows you to distinguish between similar tests using the subTest() context manager. For example, say that you have a function that checks whether a given number is even:

This function uses the modulo operator to check whether the input number is even. Here are some basic tests for the function:

These tests take positive and negative numbers and check whether the function’s result is correct by comparing it with the appropriate Boolean value. However, you’ve only tested a couple of input values. If you want to expand the input dataset, then you can use subtests.

To create a subtest, you’ll use the .subTest() method, which returns a context manager that executes the enclosed code block as a subtest. You can use this context manager to provide multiple input values for your tests.

Here’s the above example using subtests to check for multiple numbers:

In this example, you use a for loop that iterates over a list of input values. Then, you use the with statement to manage the context that .subTest() constructs. In that context, you run the assertion with the current number, which works as a subtest. Go ahead and run the test to check the results.

Exploring the Available Assert Methods

As you’ve read in previous sections, the TestCase class provides a set of assert methods. You can use these methods to check multiple conditions while writing your tests. You’ll find over twenty methods in total. They let you compare single values, such as numbers and Booleans, and collections, such as lists, tuples, dictionaries, and more.

You’ll also find a few methods that you can use to check for those situations where your code raises exceptions. You’ll learn about all these methods in the following sections.

Comparing Values

Comparing the result of a code unit with the expected value is a common way to check whether the unit works okay. The TestCase class defines a rich set of methods that allows you to do this type of check:

You’ve already seen a couple of examples that use .assertEqual(). The .assertNotEqual() method works similarly but with the opposite logic.

To illustrate how the .assertTrue() and .assertFalse() methods work, say that you have the following function:

This is a Boolean-valued function, also known as a predicate function, that returns True if the input number is prime and False otherwise. Here’s how you can test this function with unittest:

In this example, you have a TestIsPrime class with two methods. The first method tests is_prime() with a prime number, which must result in True, so you use .assertTrue() for the check. The second method tests is_prime() with a non-prime number, which must result in False, and you use .assertFalse() in this case.

If you run the script, then you get the following output:

Both tests pass successfully. So, your is_prime() function works okay. In this example, the number of tests is quite small, so you can take advantage of subtests to run is_prime() with several different numbers.

Comparing Objects by Their Identity

TestCase also implements methods that are related to the identity of objects. In Python’s CPython implementation, an object’s identity is the memory address where the object lives. This identity is a unique identifier that distinguishes one object from another.

An object’s identity is a read-only property, which means that you can’t change an object’s identity once you’ve created the object. To check an object’s identity, you’ll use the is and is not operators.

Here are a few assert methods that help you check for an object’s identity:

As you can conclude from this table, these methods are shortcuts for the is and is not operators, as suggested by the method names. With the first two methods, you can compare two objects between them. With the final two methods, you can compare an object against None, which is the Python null value.

Consider the following toy example that checks for the identity of list objects:

In the first test, you create a list containing strings. Then, you derive an alias from the original list. Aliases of an object hold a reference to the same object. So, when you compare them with .assertIs(), your check succeeds because both variables refer to the same object with the same identity.

In the second test, you create two different and independent list objects with the same data. These lists don’t point to the same object in memory, so they have different identities. In this example, you use the .assertIsNot() methods, which should succeed because the compared objects don’t have the same identity.

Go ahead and run the tests:

When you run the test_identity.py file, you’ll see that both tests pass. Identity tests like this one are useful when you’re testing a function or method that must return cached or singleton objects.

Comparing Collections

Another common need when writing tests is to compare collections, such as lists, tuples, strings, dictionaries, and sets. The TestCase class also has shortcut methods for these types of comparisons. Here’s a summary of those methods:

These methods run equality tests between different collection types. Unlike the methods in the previous section, these compare the values of objects rather than their identities.

Here’s a quick example that showcases several of these methods:

Python test_collections.py

In this example, you have several toy tests. The first test compares a tuple with a string using the .assertSequenceEqual() method because they’re both sequences. The test passes because both sequences contain the same set of characters. Note that you can’t perform this test with the .assertEqual() method, so this is a real specialized use case.

Next, you write similar tests to compare string, list, tuple, dictionary, and set objects. It’s important to highlight that the .assertDictEqual() and .assertSetEqual() methods compare their target objects using the same rules for regular comparisons between dictionaries and sets. They compare the items without considering their order in the collection.

Now, go ahead and run the test_collections.py file from your command line to check the results.

Running Membership Tests

A membership test is a check that allows you to determine whether a given value is or is not in a collection of values. You’ll run these tests with the in and not in operators. Again, the TestCase class has methods for these types of checks:

These two methods provide shortcuts for you to run membership tests on your code. Here’s a quick example of how they work:

Python test_membership.py

In the first test, you check whether a is in the list of values, b. To do this, you use the .assertIn() method. In the second test, you use .assertNotIn() to check whether the a value is not in the target list, b.

Checking for an Object’s Type

Checking the type of the object that a function, method, or callable returns may be another common requirement in testing. For this purpose, the TestClass also has dedicated assert methods:

These two methods are based on the built-in isinstance() function, which you can use to check whether the input object is of a given type.

As an example, say that you have the following class hierarchy:

In this example, you have a Vehicle class that is at the top of your class hierarchy. Then, you have two concrete classes that inherit from Vehicle and extend it with new attributes. Finally, you have a factory function that you’ll use to create instances of your classes.

To test this code with unittest, you can do something like the following:

In the first two tests, you use .assertIsInstance() to check whether the current object is an instance of Car and Truck, respectively. In the final test, you check the vehicle_factory() function. In this case, you use the base class as the comparison type.

Testing for Exceptions

Sometimes, you’ll need to check for exceptions. Yes, sometimes your own code will raise exceptions as part of its behavior. The TestCase class also provides assert methods that allow you to check for exceptions:

The first method allows checking for explicit exceptions without considering the associated error message, and the second method checks for exceptions and considers the associated message using regular expressions.

To illustrate how you can use these methods in your testing code, consider the following reimplementation of your is_prime() function:

In this updated version of is_prime(), you have two conditional statements. The first conditional ensures that the input number is an integer. If that’s not the case, then your code raises a TypeError with an appropriate error message.

The second conditional checks for input numbers greater than 2. If the input number is 1 or lower, then you raise a ValueError. The rest of the code is similar to what you’ve already seen.

How would you write tests to check that the function raises the appropriate exceptions? To do this, you can use the .assertRaises() method:

The first two tests are familiar. They cover the primary use case of your is_prime() function. The third and fourth tests check for those cases where the function gets an argument of an incorrect type. These tests specifically check for floating-point numbers and strings.

The fifth test checks for those situations where the input is 0 or 1. In those cases, the function must raise a ValueError, so that’s what the assertions catch. Finally, the sixth test checks for negative numbers, which must also raise a ValueError. You can run the test from your command line to check how they work.

The TestCase class also provides some additional assert methods that help you with warnings and logs:

Method Check
.assertWarns(warn, fun, *args, **kwds) fun(*args, **kwds) raises warn
.assertWarnsRegex(warn, r, fun, *args, **kwds) fun(*args, **kwds) raises warn and the message matches regex r
.assertLogs(logger, level) The with block logs on logger with minimum level
.assertNoLogs(logger, level) The with block does not log on logger with minimum level

The first two methods allow you to check for warnings, which are a special category of exceptions in Python. A common example of a warning is a DeprecationWarning, which appears when you use deprecated features of the language.

The final two methods allow you to handle logging. These methods return context managers to test whether a message is logged on the logger or one of its children, with at least the given level.

Using Custom Assert Methods

As with many things in Python, you can also create your assert methods to facilitate your test writing process. To do this, you can subclass TestCase and extend the class with new assertion methods.

For example, say that you frequently need to check that all the values in a list are integer numbers. In that case, you can create a test case class like the following:

This class inherits from TestCase. So, it provides all the assert methods you’ve seen so far. In addition, you extend the functionality of TestCase with a new assert method called .assertAllIntegers(), which takes a list of values and checks whether all the values are integer numbers.

Here’s how you can use this class in practice:

In this class, you define a test that uses the .assertAllIntegers() method to test a list where all the values are integers. This test must pass successfully. You can experiment with other lists where the values don’t have the correct type to see how the CustomTestCase class works.

Using unittest From the Command Line

The unittest package also provides a command-line interface (CLI) that you can use to discover and run your tests. With this interface, you can run tests from modules, classes, and even individual test methods.

In the following sections, you’ll learn the basics of how to use the unittest framework’s CLI to discover and run your tests.

Running Tests

With the command-line interface of unittest, you can run tests directly from modules, classes, and from individual test methods. The following sample commands cover those use cases:

The first command will run all the tests from test_module1 and test_module2. You can add more modules to the list if you need to. The second command allows you to run all the tests from a given TestCase class. Finally, the last command lets you run a specific test method from a given TestCase class.

As an example, run the following command to execute the tests in your test_age.py file:

Note that for this command to work, the target module must be available in the import path of your current Python environment. Otherwise, you’ll get an import error. To avoid this issue, you can also run the tests by passing in the module’s file path:

With this command, unittest will find and load the test module from the provided path, which saves you from getting an import error.

Discovering Tests Automatically

The unittest framework supports test discovery. The test loader can inspect each module in a given directory looking for classes derived from TestCase. Then, the loader groups the found classes within a complete test suite.

For example, to discover and run all the tests that you’ve written so far, run the following command in the directory that contains them:

This command locates all tests in the current directory, groups them in a test suite, and finally runs them. You can use the python -m unittest as a shortcut for the above command.

You can use the -s or --start-directory command-line options with the discover subcommand to specify the directory where your tests reside. Other command-line options of discover include:

Option Description
-v, --verbose Produces a verbose output
-p, --pattern Allows for using glob patterns and defaults to test*.py
-t, --top-level-directory Defines the top-level directory of a project

With these command-line options, you can tweak your test discovery process to fulfill your test automation needs.

Using Command-Line Options

You’ll also find that the unittest CLI supports several command-line options. Here’s a summary of them:

Option Description
-v Shows a more verbose output
-b, --buffer Buffers the standard output and error streams during the test execution
-c, --catch Waits for the current test to run and reports all the results up to the point Ctrl+C is pressed during the test execution
-f, --failfast Stops the test run on the first error or failure
-k Only runs test methods and classes that match the pattern or substring
--locals Shows local variables in tracebacks
--durations N Shows the N slowest test cases (N=0 for all)

With these command-line options, you can fine-tune how your tests run. In day-to-day test running, the -v option is probably the most commonly used. This option works the same as setting the verbosity argument to 2 when calling the unittest.main() function.

Grouping Your Tests With the TestSuite Class

The unittest framework has a class called TestSuite that you can use to create groups of tests and run them selectively. Test suites can be useful in many situations, including the following:

To illustrate how to create test suites, say that you have a module called calculations.py that defines basic arithmetic and statistical operations:

In this module, you have several functions. Some of them are basic arithmetic operations, and others are statistical operations. From a testing point of view, a good approach is to write the following test cases:

Python test_calculations.py

These tests work as expected. Suppose you need a way to run the arithmetic and statistical tests separately. In this case, you can create test suites. In the following sections, you’ll learn how to do that.

Creating Test Suites With the TestSuite() Constructor

The TestSuite class allows you to create test suites. The class constructor takes the tests argument that must be an iterable of tests or other test suites. So, you can create your test suites like in the following code snippet:

Python test_calculations.py

In the make_suite() function, you create a list of all tests from the TestArithmeticOperations test case. Then, you create and return a test suite using the TestSuite() constructor with the list of tests as an argument.

To run the suite, you create a TextTestRunner and pass the suite to its .run() method. If you run this file, then you get the following output:

This command only runs the four tests in the suite, skipping the rest of the tests in the test_calculations.py file.

Adding Tests to a Suite: .addTest() and .addTests()

You can also use the .addTest() method to add individual tests to an existing suite. To do this, you can do something like the following:

This new version of make_suite() works like the previous section. Instead of using the class constructor to build the test suite, you use the .addTest() method. This approach can be useful when you have an existing test suite, and you need to add more tests to it.

The TestSuite class also has a .addTests() method that you can use to add several tests in one go. This method takes an iterable of test cases, test suites, or a combination of them. Consider the following example that creates a test suite with the statistical tests:

Python test_calculations.py

In this example, you create a list of tests from the TestStatisticalOperations class. Then, you create a TestSuite() instance and add the list of tests using .addTests(). Finally, you return the test suite as usual.

Go ahead and run the file from your command line:

This time, the command only runs the statistical tests. As you can see, test suites are a great way to run tests selectively.

Creating Suites With the load_tests() Function

Adding tests to a suite manually can be a tedious task. It can also be error-prone and represent a maintenance burden. Fortunately, unittest has other tools that can help you create test suites quickly.

The load_tests() function is one of these tools. The function is a hook that unittest provides for customizing test loading and suite creation, either for modules or packages of tests.

The function takes three mandatory arguments. Here’s the signature:

The loader argument will hold a test loader, which normally is an instance of TestLoader. When you define load_tests() in a module, the standard_tests argument will receive the tests that are loaded from the module by default. When you define the function in a package, the standard_tests will get the test loaded from the package’s __init__.py file. Finally, the pattern argument is a glob pattern that you can use when discovering the tests.

When you call unittest.main() in a test module, the load_tests() function defined in the module gets called automatically, and unittest takes care of passing in the required arguments. This behavior allows you to build a test suite with minimal boilerplate code.

Here’s an example of how to create two test suites. One for the arithmetic tests and another for the statistical tests:

Python test_calculations.py

After you create the test suite, you use the .addTests() method to add multiple tests in one go. To build the list of tests, you call the .loadTestsFromTestCase() on the loader argument, which is the default test loader.

Note that in this example, you don’t use the standard_tests or the pattern argument. The former is useful in test packages where you’d like to add tests to those found in __init__.py and build a test suite from there. The pattern argument is also for loading tests from packages rather than from modules.

Creating Test Fixtures

A test fixture is a preparation that you perform before and after running one or more tests. The preparations before the test run are known as setup, while the tasks that you perform after the test run are called teardown.

The setup process may involve the creation of temporary files, objects, databases, dataframes, network connections, and so on. In contrast, the teardown phase may require releasing resources, removing temporary files, closing connections, and similar tasks.

The unittest framework allows you to create setup and teardown fixtures in your test cases classes by overriding the following methods in your TestClass subclasses:

Method Description
.setUp() An instance method that unittest calls before running each test method in a test case class.
.tearDown() An instance method that unittest calls after running each test method in a test case class.
.setUpClass() A class method that unittest calls before running the tests in a test case class.
.tearDownClass() A class method that unittest calls after running the tests in a test case class.

The last two methods are class methods, which means that you need to use the @classmethod decorator to create them. Here’s how they should look:

These methods only take the current test class as an argument. Remember that they run only once per class.

To create module-level fixtures, you need to use module-level functions rather than methods on a TestCase subclass. The required functions are the following:

If an exception occurs in the setUpModule() function, then none of the tests in the module run, and the tearDownModule() function won’t run either.

In the following sections, you’ll learn how to create fixtures using the capabilities of unittest.

Test Fixtures

As an example of how to use fixtures, say that you have the following implementation of a stack data structure:

In this module, you define the Stack class that provides the .push() and .pop() methods. The former method appends a new item to the top of the stack, while the latter method removes and returns the item at the top of the stack.

Then, you implement three special methods. The .__len__() method supports the built-in len() function. The .__iter__() method allows you to use Stack instances in for loops. Finally, the .__reversed__() method allows you to support the built-in reversed() function.

Now, say that you need to write a TestCase subclass to test this class. The test class will have to test all the features of your Stack class. In this situation, you have at least two options:

  1. You can create a new an independent instance of Stack for every test.
  2. You can create a single instance of the class for all the tests.

The first approach sounds tedious and may require a lot of repetitive code. The second approach sounds better, so you decide to go with it. In this situation, you can use a class-level setup fixture to create the instance of Stack and reuse it in every test:

In this test module, you create the TestStack to test the different features of your Stack class. The first two methods define the setup and teardown logic, which consist of creating and removing an instance of Stack, respectively.

When you run the tests, unittest automatically calls .setUp() and .tearDown() before and after running each test method. This way, every test method has a fresh instance to work on. In other words, these two methods save you from creating a fresh instance of Stack in each test method.

Class-Level Fixtures

If you use the .setUpClass() and .tearDownClass() class methods, then you can create class-level fixtures. This type of fixture only runs once per test case class. The .setUpClass() method runs before the test methods, and .tearDownClass() runs after all the test methods have run.

This behavior is known as shared fixtures because all the test methods depend on a single setup and teardown run. Note that shared fixtures break test isolation. In other words, the results of a test will depend on previously run tests. So, they should be used with care.

To illustrate how class-level fixtures work, say you have the following Employee class:

The class represents an employee of your company. The class has four attributes to store information about each employee. Because you’re planning to create a large number of instances of this class, you use the .__slots__ attribute to reduce the memory footprint.

The employees’ data lives in a CSV file that looks something like the following:

You need to define a function that reads this file and returns a list of employees. This function can look something like the following:

In the from_csv_file() function, you read a CSV file using the DictReader from the csv module. Then, you run a loop to create a list of Employee instances with the read data. Now, you want to write tests for this function:

In this TestFromCsvFile, you have the .setUpClass() method. In this method, you create a new temporary file using NamedTemporaryFile from the tempfile module.

To populate this file, you use the sample data stored in the SAMPLE_CSV constant. After wiring the sample data to the temporary file, you close the file and call from_csv_file() to create the list of employees. With these actions, you’ve set up everything to test the function. Note that all these actions run only once before all the test methods run.

In the tearDownClass(), you remove the temporary file to clean up your file system and free the acquired resources. This method will run after the test methods have run. Finally, you have three test methods that check different aspects of the generated list of employees.

Module-Level Fixtures

You can also create module-level fixtures. To make these types of fixtures, you need to define the following functions at the module level:

These fixtures run once per module. The setup fixture runs before all the test cases in the module, and the teardown fixture runs after all the test cases in the module have run.

If an exception happens in the setUpModule() function, then none of the tests in the module will run, and the tearDownModule() won’t run either. If the raised exception is a SkipTest exception, then the module will be reported as skipped instead of an error.

Module-level fixtures are useful when you have several TestCase subclasses in a module, and some of them will benefit from a common setup and teardown logic.

The classic example is a test module with a few test cases that check for database-related functionalities. These tests may need an active connection to the database, which you can create in the setUpModule() function and close in the tearDownModule() function.

Debugging Failing Tests

Up to this point, you haven’t dealt with failing tests in unittest. However, failing tests are probably the most important part of any code testing process. Failing tests allow you to fix your code and make it work as expected. The unittest framework provides descriptive outputs for failing tests. These outputs can help you debug your code and fix it.

A Quick Example: FizzBuzz

Consider the following function that tries to solve the FizzBuzz challenge, where you return "fizz" for numbers divisible by 3, "buzz" for those divisible by 5, and "fizz buzz" for those divisible by both 3 and 5.

Go ahead and create a fizzbuzz.py file and add the following code to it:

This function works okay for numbers divisible by 3 or 5. However, there is a slight issue with numbers that are divisible by both.

Here’s a unittest test case for your fizzbuzz() function:

The first two tests will pass. However, the third test won’t pass because the function has a bug. Go ahead and run the tests so that you can check the output of a failing test:

In this output, the first highlighted line points you to the failing test. The second highlighted line lets you know the exact line where you can find the failing assertion. Finally, the third highlighted line tells you that the actual result and the expected result are unequal.

The failing test tells you that the fizzbuzz() function has an issue with numbers that are divisible by both 3 and 5. So, you need to modify the code to make the test pass:

In this update, you moved the condition that checks for numbers divisible by 3 and 5 to the beginning of the function. Now you can run the tests again:

Your fizzbuzz() function works correctly after the update. This quick example uncovers the essence of code testing, where you write tests to ensure that your code works as expected so you can fix any issue that the tests reveal.

A Test-Driven Example: Rock, Paper, and Scissors

A well-known philosophy around code testing is the test-driven development (TDD) methodology. In TDD, you convert the code’s requirements into test cases before you write the actual code.

For example, say that you want to code a function that takes a number from 0 to 2 and return the strings "rock", "paper", or "scissors". Instead of writing the function right away, you start writing the following tests:

The first test provides a way to check whether the function validates the input argument, which must be a number between 0 and 2. The other tests check the three possible inputs and the expected output for each of them.

Now you can write your function to gradually pass the tests:

In this snippet, you wrote the code to pass the first test. Go ahead and run the tests from your command line:

In this output, the first line contains the .FFF text. The dot means that you have a passing test, which is the test_out_of_range() test. The Fs mean that you have three failing tests, which are those that deal with the function’s primary goal.

Next, you can write the required code to pass the rest of the tests:

In this snippet, you add the required code to pass the test_rock() test. Now you can run the tests again:

This time you have two failing tests, the test_paper() and test_scissors() tests. During the development, you realize that there’s a better way to code the function:

In this final implementation of rock_paper_scissors(), you have a list containing the possible return values. Then, you use the input argument to index the list and return the appropriate string.

Go ahead and run your tests again:

Great! Your function passes all the tests. To code this function, you’ve used an incremental development process driven by test cases. At the end of the process, your code works as expected. So, it’s ready to use!

Testing With Fake Objects: unittest.mock

The unittest.mock module provides two base classes that allow you to mock, or simulate, external resources, such as files, connections, and so on:

  1. Mock is a general generic mock object.
  2. MagicMock is the same as Mock, but it includes magic methods.

As a quick example of how to mock an object, consider the following sample module:

This module defines the is_weekday() function, which returns True if the current day is a weekday and False otherwise. There’s no easy way to test this function because time never stops. If you write the test today, then that test can fail in a few days because the result of calling datetime.date.today() changes every day.

To pass this challenge and make your tests work, you can mock the .today() method to return a fixed date. Here is a test module that does that:

In this module, you write a test case class with two test methods. You use the @patch decorator to create a Mock object around the datetime module. Note that this is patching the datetime module that you imported there in your weekday module. This Mock object goes to the mock_datetime argument of the underlying test method.

Then, you create fake return values for .today() in each test method. The fake return value in .test_is_weekday() is a Thursday, so is_weekday() must return True and that’s what you check. Similarly, the fake return value in .test_is_weekend() is a Saturday, and is_weekday() must return False.

That’s it! Now, your tests will always work, regardless of the date, because you’ve patched the default behavior of .today() to make it return a fixed date.

Conclusion

Now you know how to write unit tests for your Python code using the unittest testing framework. This framework comes with the Python standard library, so you don’t have to install third-party packages to start writing your tests.

The unittest framework supports several useful features that you’ve also learn how to use. These features include test cases, fixtures, test suites, test discovery, and more.

In this tutorial, you’ve learned how to:

With this knowledge, you’re ready to start writing robust unit tests for your Python code without needing to install additional packages.

Take the Quiz: Test your knowledge with our interactive “Python's unittest: Writing Unit Tests for Your Code” quiz. You’ll receive a score upon completion to help you track your learning progress:


Python's unittest: Writing Unit Tests for Your Code

Interactive Quiz

Python's unittest: Writing Unit Tests for Your Code

In this quiz, you'll test your understanding of Python testing with the unittest framework from the standard library. With this knowledge, you'll be able to create basic tests, execute them, and find bugs before your users do.