top of page

Elevate Your Python Programming Skills with Unit Testing!

Are you tired of spending hours debugging your Python code, hunting down those elusive bugs? Do you wish there was a way to write robust, reliable, and bug-free code from the start? Look no further! This article will explore a powerful tool that can transform your coding experience: Python's built-in "unittest" module.


Unit testing is a critical practice in software development that allows you to verify the correctness of your code's individual components or units. It helps you catch bugs early on, improve code quality, and ensure your program behaves as expected in different scenarios. Whether you're a beginner or an experienced Python developer, mastering the art of unit testing is essential for writing cleaner, more reliable code.


Throughout this article, we will dive into the basics of using Python's "unittest" module to write effective tests for your code. We'll start with the fundamentals, then guide you through setting up test cases and asserting expected outcomes. We'll also cover best practices and provide practical examples to illustrate each concept.


By the end of this article, you'll have a solid foundation in using Python's "unittest" module, equipping you with the skills to write bug-free code and save valuable time in the long run. So, if you're ready to take your Python coding to the next level, let's dive in and learn the secrets of writing reliable, error-free programs with the "unittest" module!



Python's "assert" Statement

The assert statement is commonly used in testing scenarios to verify that certain conditions hold during the execution of a program.


The general syntax of the assert statement is as follows:

Here, "condition" is the expression or condition you want to check, and "message" is an optional string that can provide additional information or clarification about the assertion.


Python first evaluates the condition. If the condition evaluates to True, the program continues execution. However, if the condition evaluates to False, Python raises an AssertionError exception, halting the program's execution.


Here's an example to illustrate the usage of assert:

The assert statement does not replace error handling, such as using the try and except block. The assert statement should only be used for debugging and testing your code.



Built-in Module for Unit Tests in Python

Unit testing is a software testing technique where individual code units, such as functions or methods, are tested to ensure they behave as expected. These tests are typically isolated from the rest of the system, focusing on specific inputs and expected outputs.


The "unittest" module offers a framework for organizing and running these tests, making it easier for developers like you to write effective unit tests for your Python programs. The "unittest" module is part of Python's standard library, so there's no need to install additional packages.


Below is a list of standard assert methods provided by a class called"TestCase." For a complete list of assert methods, see the official documentation.

Method

Checks that

assertEqual(a, b)

a == b

assertNotEqual(a, b)

a != b

assertTrue(x)

bool(x) is True

assertFalse(x)

bool(x) is False

assertIn(a, b)

a in b

assertIsInstance(a, b)

isinstance(a, b)


Project Setup

Before any tests can be created, we need some code to run tests on. Below, I provide my current project structure. Here, you can see I have a module called "src," and in this module, I have a Python file called "calculator."

project
├── .venv
└── src
    ├── __init__.py
    └── calculator.py

Here's the code within the "calculator" Python file:

This example class represents a basic calculator. It has four methods: add_nums, subtract_nums, multiply_nums, and divide_nums, which perform addition, subtraction, multiplication, and division operations, respectively. The class takes two numbers as arguments when creating an instance (object) of the class. These numbers are stored as instance variables (num1 and num2) of the object. The methods use these instance variables to perform the corresponding arithmetic operations and return the results.



Writing Your First Unit Test in Python

Create a new module named "tests" in your project. This is simply a folder with a Python file called "__init__". Within this new module, create a Python file named "test_calculator."


I now have the following project structure:

project
├── .venv
├── src
│   ├── __init__.py
│   └── calculator.py
└── tests
    ├── __init__.py
    └── test_calculator.py

If you're wondering why the "__init__" Python file is needed, check out this article I wrote about running unit tests from the command line.



To test the "Calculator" class methods, we need to create a new class that inherits from "TestCase" provided by the "unittest" module.

Within this testing class, we create methods that execute code to test parts of our program. This article will test the four methods in the "Calculator" class. It's important to note that each test method you create in the "TestCalculator" class should always have the word "test" at the start of each name.


You create a test method like any other method you'd add to a class. Below is an example unit test within the "TestCalculator" class.

The Python class "TestCalculator" is used to write and run unit tests for the "Calculator" class. It checks if the addition method works correctly by creating a "Calculator" object with the numbers 2 and 4, then calling the "add_nums()" method on it. It verifies if the result is equal to 6 using the "assertEqual" method provided by the "unittest" module and displays an error message if the sum is incorrect.


The "assertEqual" method (including most other assert methods) takes in two required arguments named "first" and "second" with one additional optional parameter called "msg." The first argument is your expected value, and the second is the actual value produced by your program. You can flip these two around, but it might make the output of a test failure confusing.


To run the actual tests, you need to call the "main" method defined in the "unittest" module. On line 18 in the example above, you can see we placed this call in the classic "if name == main" condition. We place the method call inside the if statement because it allows us to execute the tests from the terminal and the IDE.


Running this script above outputs the following:

Ran 1 test in 0.001s
OK


Let's say that we changed the "add_nums()" method to the following:

Now when we run the test, we get this output:

Ran 1 test in 0.009s

FAILED (failures=1)

The sum is incorrect
8 != 6

Expected :6
Actual   :8

This error message is saying that the test method expected the first value given in the "assertEqual" method, a six in this case, to be equal to the returned value from our "add_nums()" method. The two values were not equal, so the test failed.


Using the "test_add_nums" method as a template, we can quickly create tests for the remaining three methods in the "TestCalculator" class.

After adding the last three methods into the "TestCalculator" class and running the test script, we get the following output:

Ran 4 tests in 0.002s
OK


Removing Redundancies

I'm sure you've noticed that in each test method, we use the exact same line of code to create an instance of the "Calculator" class. In the same way, it's bad practice to repeat code in your main program; the same holds true when writing test cases.


To clean up the code, we can use a method called "setUp" in our "TestCalculator" class.

This method runs once before every test method within your test class. In the case of this example, the "setUp" method would run four times, once before each test method.


You can then access the "Calculator" instance in your test methods with the following line of code:


While this improves our testing code, object creation is an expensive task. In this example, creating the same object repeatedly before each test doesn't make much sense. This will only slow down test execution. While the effects are not noticeable now, they will be as your test cases increase. A better way to set up our test cases would be to create the object once before any test case is run and use that same object throughout all test methods. Luckily, Python's "unittest" module provides a method called "setUpClass" to do just that.

You can change "cls" to "self" if you like.


The "TestCalculator" class now looks like this:


Code Clean Up

With this simple example, we don't have any code to clean up because Python does all the heavy lifting for us. If, however, you do need to "clean up" your code, for example, close files or remove newly created items, you can use either the "tearDown" or "tearDownClass" methods within your test class.


Here's the basic syntax for each method:


Similar to the "setUp" and "setUpClass" methods, the "tearDown" method will run after each test completes, and the "tearDownClass" method will run once every test has been completed.



Conclusion

Unit testing is a crucial practice in software development that brings numerous benefits. In this article, we explored the basics of unit testing in Python using the "unittest" module. We learned how to set up the test environment, write test cases, and interpret test results.


By incorporating unit testing into your development workflow, you can enhance the reliability of your Python programs. Unit tests help identify and fix bugs early, reducing the chances of critical issues in production. They also serve as documentation and provide a safety net when making changes to the codebase.


Remember to write test cases covering different scenarios and edge cases, ensuring thorough code testing. Embrace the principles of simplicity, clarity, and readability when writing tests, making them easy to understand and maintain.


Incorporating unit testing and adopting a test-driven mindset will boost the quality of your code and enhance your confidence as a developer. So, embrace the power of unit testing and make it an integral part of your Python development journey.



Frequently Asked Questions

What is unit testing in Python?

Unit testing in Python involves testing individual code units, such as functions or methods, to ensure they behave as expected. It helps identify bugs early and improves code quality and stability.

Why should I use the "unittest" module?

What are some best practices for unit testing?


10 views0 comments
bottom of page