Home / Blog /
Unit testing with Python: The basics and a quick tutorial
//

Unit testing with Python: The basics and a quick tutorial

//
Tabnine Team /
6 minutes /
April 25, 2024

What is unit testing with Python? 

Unit testing is a type of software testing where individual components of a software are tested. The purpose is to validate that each unit of the software performs as designed. In the context of Python, a unit could be an individual function, method, class, module, or any other small and well-defined component of the software.

Unit testing with Python primarily involves writing test cases for these individual units, running these tests, and examining the results. This process ensures that each component of the software behaves as expected under a variety of conditions. There are several libraries commonly used for unit testing in Python, including the built-in unittest library, pytest, and TestProject.

Why do you need unit tests in Python? 

Refactoring or modifying existing code

Refactoring involves changing the structure of the code without altering its functionality. This is a common practice used to improve the design, readability, and maintainability of code.

When you’re refactoring, you need to make sure that the changes you make don’t inadvertently break something. This is where unit tests come in. Before you make any changes, you can run the existing unit tests to ensure everything is working fine. After making your changes, you run the tests again to ensure your changes haven’t broken anything.

Validating bug fixes

When a bug is found, a common first step is to write a unit test that exposes the bug. You then fix the bug and rerun the unit test. If it passes, you know that the bug has been fixed.

This not only ensures that the bug has been properly fixed but also helps prevent the bug from reoccurring in the future. The unit test acts as a safety rail, ensuring that the same issue doesn’t creep back into your codebase unnoticed.

CI/CD

Unit testing with Python also plays a crucial role in continuous integration/continuous deployment (CI/CD) pipelines. In a CI/CD pipeline, code changes are automatically built, tested, and prepared for release.

Unit tests are an important part of this pipeline. Every time a change is made to the codebase, the unit tests are run automatically. When tests fail, this can break the build, stopping the delivery pipeline and alerting developers to the problem. 


Unit testing frameworks for Python 

unittest (built-in)

Python comes with a built-in testing framework called unittest. The unittest module provides a rich set of tools for constructing and running tests. This includes a test loader for discovering and loading tests, a test runner for running them, and a test suite that groups tests together.

unittest supports a wide range of testing scenarios and is flexible enough to support moderately complex testing needs. However, it can be a bit verbose and its use of camel case can feel out of place to developers used to Python’s snake case naming convention.

pytest

pytest is a popular third-party testing framework for Python. It’s known for its simple, easy-to-write syntax. One of the primary features of pytest is its ability to support simple unit tests alongside complex functional tests. It also supports parameterized testing, which allows you to run a test function multiple times with different arguments.

TestProject

TestProject is another Python testing framework. It’s an open source, end-to-end testing framework designed for web and mobile applications. TestProject is easy to get started with, provides a simple API for writing tests, and integrates well with other tools and frameworks. It also comes with a powerful reporting feature, which is useful for large test suites.

DocTest

DocTest is a testing framework that extracts tests from the documentation of your code. This makes it particularly useful for testing the correctness of your code examples in the documentation.

The main advantage of DocTest is that it helps ensure that your documentation stays up-to-date with your code. However, it’s not as flexible or powerful as some of the other testing frameworks and is best used in conjunction with another testing framework.

Nose2

Nose2 is the successor to the popular Nose testing framework. It extends unittest to make testing easier and more flexible. Nose2 supports a wide range of testing needs and comes with numerous plugins for things like test coverage, pretty output, and test attribute support. However, it’s a bit more complex than some of the other frameworks and may be overkill for simple testing needs.

Tutorial: Writing a unit test in Python with the unittest framework 

Creating test classes

Before we start writing tests, we first need to understand the structure of a test case in Python. A test case is a single unit of testing. It checks for a specific response to a particular set of inputs. In the unittest framework, a test case is represented by unittest.TestCase instances.

To write a test case, we first create a class that inherits from unittest.TestCase. The class can contain methods, where each method represents a single test case. Here’s an example:

import unittest

class TestMyFunction(unittest.TestCase):

    pass

 

In this example, TestMyFunction is a test case class that inherits from unittest.TestCase. It doesn’t contain any test methods yet, but we’ll add those in the next section.

Writing test methods

Now that we have our test case class, we can start adding test methods. A test method is a function that performs a specific test on your code. In the unittest framework, a test method is a method inside a test case class. It begins with the word test.

Let’s add a test method to our test case class:

import unittest




class TestMyFunction(unittest.TestCase):

    def test_addition(self):

        self.assertEqual(add(1, 2), 3)

In the example above, test_addition is a test method. It tests whether the add function returns the correct output when given the inputs 1 and 2. The self.assertEqual statement is an assertion. Assertions are the conditions that we check in our tests.

Running tests with unittest

With our test case class and test methods in place, we can now run our tests. The unittest framework provides a command line interface for running tests. To run your tests, you can use the unittest  command followed by the name of your test module.

Here’s how you can run your tests:

if __name__ == '__main__':

    unittest.main()

This code runs all the test cases in the module. If all the tests pass, it prints “OK.” If any test fails, it prints “FAIL” and gives a detailed report of what went wrong.

Assertions in unittest

Assertions are the heart of our tests. They are the conditions that we check to determine whether a test passes or fails. The unittest framework provides a variety of assertion methods, allowing us to test for different conditions.

These are some of the most commonly used assertion methods:

  • assertEqual(a, b): This asserts that a equals b.
  • assertTrue(x): This asserts that x is true.
  • assertFalse(x): This asserts that x is false.
  • assertRaises(exc, fun, *args, **kwds): This asserts that the function fun raises the exception exc when called with arguments *arg and **kwds.

Here’s an example that uses several assertion methods:

import unittest

class TestMyFunction(unittest.TestCase):

    def add(self, x, y):

        return x+y


    def test_addition(self):

        self.assertEqual(self.add(1, 2), 3)

        self.assertTrue(self.add(1, 2) > 0)

        self.assertFalse(self.add(1, 2) < 0)

        self.assertRaises(TypeError, self.add, "1", "2")

In this example, test_addition uses four different assertion methods to test different conditions. This shows the flexibility of the unittest framework.

Automate unit testing in Python with Tabnine

Recent advances in generative AI can be a big help to development teams when creating unit tests. Tabnine is an AI coding assistant that helps development teams of every size use AI to accelerate and simplify the software development process without sacrificing privacy, security, or compliance. Tabnine boosts engineering velocity, code quality, and developer happiness by automating the coding workflow through AI tools customized to your team. Tabnine supports more than one million developers across companies in every industry. 

Unlike generic coding assistants, Tabnine is the AI that you control:

It’s private. You choose where and how to deploy Tabnine (SaaS, VPC, or on-premises) to maximize control over your intellectual property. Rest easy knowing that Tabnine never stores or shares your company’s code.  

It’s personalized. Tabnine delivers an optimized experience for each development team. It’s context-aware and can be tuned to recommend based on your standards. You can also create a bespoke model trained on your codebases.

It’s protected. Tabnine is built with enterprise-grade security and compliance at its core. It’s trained exclusively on open source code with permissive licenses, ensuring that our customers are never exposed to legal liability.

Here are a few examples showing how Tabnine can be used to generate unit tests:

Tabnine accelerates and simplifies the entire software development process with AI agents that help developers create, test, fix, document, and maintain code. In addition to generating code, Tabnine can also automatically generate and run unit tests, helping developers increase test coverage, and catch issues earlier in deployment. If tests fail or bugs emerge, Tabnine will help diagnose the issue and propose code suggestions to fix the problem. 

Tabnine Chat can help with every stage of development, right in your IDE:

  • Answering questions regarding your code
  • Generating new code from scratch
  • Explaining a piece of code
  • Searching your code repos for specific functions or pieces of code
  • Refactoring code
  • Generating documentation (docstrings)
  • Finding and fixing code issues
  • Generating unit tests and more