Home > Backend Development > Python Tutorial > A Guide to Python Unit Testing with unittest and pytest

A Guide to Python Unit Testing with unittest and pytest

William Shakespeare
Release: 2025-02-19 08:57:13
Original
825 people have browsed it

A Guide to Python Unit Testing with unittest and pytest

This article explores the significance of software testing and why you should pay attention to it. We will learn how to design unit tests and how to write Python unit tests. In particular, we will explore two of the most commonly used unit testing frameworks in Python: unittest and pytest.

Key Points

  • Unit testing is a crucial part of software development, allowing developers to test specific components or units of a program to ensure they run as expected. Popular unit testing frameworks in Python include unittest and pytest.
  • Well-designed unit tests should be fast, independent, repeatable, reliable and well-named. The “Preparation, Execution, Assertion (AAA)” mode is often used to organize unit tests, separating setup, execution, and verification.
  • The unittest framework is part of the Python standard library and is inspired by JUnit, the unit testing framework in Java. It uses a special assertion method and requires that the test be written as a method of a class inherited from the unittest.TestCase class.
  • The pytest framework allows for complex testing with less code, supports unittest test suites, and provides over 800 external plug-ins. Unlike unittest, pytest uses normal Python assertion methods to make it simpler and more intuitive.
  • Although unit testing has many advantages, it must be remembered that testing can only prove the existence of defects, but not the absence of defects. Even if all tests pass, it cannot prove that the software system has no errors.

Introduction to Software Testing

Software testing is a process of checking the behavior of software products to evaluate and verify that they comply with specifications. A software product may contain thousands of lines of code and hundreds of components that work together. If a line of code does not work properly, the error may propagate and cause other errors. Therefore, to ensure that the program runs as expected, it must be tested.

Because modern software can be quite complex, there are multiple levels of testing to evaluate different aspects of correctness. According to the ISTQB certification test basic level syllabus, there are four levels of software testing:

  1. Unit Test: Test specific lines of code
  2. Integration Test: Test integration between multiple units
  3. System test: test the entire system
  4. Acceptance test: Check whether it meets business objectives

This article will discuss unit testing, but before going into it, I would like to introduce an important principle in software testing.

Tests can only prove the existence of defects, but cannot prove the absence of defects.

ISTQB CTFL Syllabus 2018

In other words, even if all the tests you run do not show any failures, it will not prove that your software system is free of errors, or another test case will not find any flaws in your software behavior.

What is unit testing?

This is the first test level, also known as component testing. In this section, a single software component is tested. Depending on the programming language, a software unit may be a class, a function, or a method. For example, if you have a Java class called ArithmeticOperations that contains multiple and divide methods, unit testing of the ArithmeticOperations class needs to test the correct behavior of the multiply and divide methods.

Unit testing is usually performed by software testers. To run unit tests, software testers (or developers) need to access the source code because the source code itself is the object being tested. Therefore, this software testing method that directly tests the source code is called white box testing.

You may be wondering why you should worry about software testing and whether it is worth it. In the next section, we will analyze the motivations behind the testing software system.

Why should you perform unit tests

The main advantage of software testing is that it improves the quality of the software. Software quality is crucial, especially in the world where software handles all kinds of things in our daily activities. Improving software quality is still an overly vague goal. Let's try to better illustrate what we call the quality of software. According to ISO/IEC standard 9126-1 ISO 9126, software quality includes the following factors:

  • Reliability
  • Functional
  • Efficiency
  • Availability
  • Maintainability
  • Porability

If you own a company, then you should carefully consider software testing activities as it will affect your business. For example, in May 2022, Tesla recalled 130,000 cars due to problems with the vehicle’s infotainment system. This problem was then solved through a software update distributed "in the air". These failures have caused time and money to the company and also caused problems for customers, as they were unable to use their cars for a while. Testing software does cost money, but companies can also save millions of technical support.

Unit testing focuses on checking that the software is running correctly, which means checking that the mapping between input and output is done correctly. As a low-level testing activity, unit testing helps identify errors early so that they do not propagate them to higher levels of the software system.

Other advantages of unit testing include:

  • Simplified integration: It is easier to solve integration problems by ensuring that all components work independently.
  • Minimize code regression: With a large number of test cases, it is easier to find problems if some modifications to the source code in the future will cause problems.
  • Provided documentation: By testing the correct mapping between input and output, unit testing provides documentation on how to use the method or class being tested.

Design testing strategies

Now let's see how to design a test strategy.

Definition of test scope

Before starting to plan your testing strategy, there is an important question to answer. What parts of the software system do you want to test?

This is a key issue, because exhaustive testing is impossible. Therefore, you can't test all possible inputs and outputs, but you should prioritize the tests based on the risks involved.

A number of factors need to be considered when defining the scope of the test:

  • Risk: What business consequences will occur if the error affects this component?
  • Time: How long do you want the software product to be ready? Do you have a deadline?
  • Budget: How much money are you willing to invest in testing activities?

Once you have defined the test scope (specifying what you should test and what you should not test), you can discuss the features that a good unit test should have.

Features of unit tests

  • Fast. Unit tests are mostly performed automatically, which means they have to be fast. Slow unit tests are more likely to be skipped by developers because they do not provide instant feedback.
  • Independent. Unit tests are independent by definition. They test individual code units and do not rely on any external factors (such as files or network resources).
  • Repeatable. Unit tests are executed repeatedly and the results must be consistent over time.
  • Reliable. Unit tests will fail only if there is an error in the system under test. The order in which the environment or tests are executed should not be important.
  • Correctly named. The name of the test should provide relevant information about the test itself.

A step is missing before delving into unit testing in Python. How do we organize the tests to make them clean and readable? We use a pattern called Preparation, Execution, and Assertion (AAA).

AAA mode

The Preparation, Execution, and Assertion (AAA) pattern is a common strategy for writing and organizing unit tests. It works as follows:

  • In the preparation phase, set all objects and variables required for the test.
  • Next, during the execution phase, the function/method/class to be tested is called.
  • Finally, in the assertion phase, we verify the results of the test.

This strategy provides a clean way to organize unit tests by separating all the main parts of the test (setup, execution, and validation). Additionally, unit tests are easier to read because they all follow the same structure.

Unit tests in Python: unittest or pytest?

We will now discuss two different unit testing frameworks in Python. These two frameworks are unittest and pytest.

unittest Introduction

The Python standard library contains the unittest unit testing framework. This framework is inspired by JUnit, which is a unit testing framework in Java.

As stated in the official documentation, unittest supports several important concepts we will mention in this post:

  • Test case, this is a single unit of test
  • Test suite, this is a set of test cases executed together
  • Test runner, which is the component that handles the execution and results of all test cases

unittest has its own way of writing tests. In particular, we need:

  1. Writing our tests as a method of class inheriting from the unittest.TestCase class
  2. Use special assertion method

Since unittest is already installed, we are ready to write our first unit test!

Writing unit tests using unittest

Suppose we have the BankAccount class:

import unittest

class BankAccount:
  def __init__(self, id):
    self.id = id
    self.balance = 0

  def withdraw(self, amount):
    if self.balance >= amount:
      self.balance -= amount
      return True
    return False

  def deposit(self, amount):
    self.balance += amount
    return True
Copy after login
Copy after login

We cannot withdraw money that exceeds the available amount of deposit, so let's test if our source code handles this situation correctly.

In the same Python file, we can add the following code:

class TestBankOperations(unittest.TestCase):
    def test_insufficient_deposit(self):
      # Arrange
      a = BankAccount(1)
      a.deposit(100)
      # Act
      outcome = a.withdraw(200)
      # Assert
      self.assertFalse(outcome)
Copy after login
Copy after login

We are creating a class called TestBankOperations which is a subclass of unittest.TestCase. In this way, we are creating a new test case.

In this class, we define a single test function whose method begins with test. This is important because every test method must start with the word test.

We expect this test method to return False, which means the operation failed. To assert the result, we use a special assertion method called assertFalse().

We are ready to perform the test. Let's run this command on the command line:

python -m unittest example.py
Copy after login
Copy after login

Here, example.py is the name of the file containing all the source code. The output should look like this:

<code>.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK</code>
Copy after login
Copy after login

Very good! This means our test was successful. Now let's see what the output looks like when there is a failure. We add a new test to the previous class. Let's try to deposit negative amounts, which is certainly impossible. Will our code handle this situation?

This is our new test method:

  def test_negative_deposit(self):
    # Arrange
    a = BankAccount(1)
    # Act
    outcome = a.deposit(-100)
    # Assert
    self.assertFalse(outcome)
Copy after login
Copy after login

We can use the detailed mode of unittest to perform this test by using the -v flag:

python -m unittest -v example.py
Copy after login
Copy after login

The output is different now:

<code>test_insufficient_deposit (example.TestBankOperations) ... ok
test_negative_deposit (example.TestBankOperations) ... FAIL

======================================================================
FAIL: test_negative_deposit (example.TestBankOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "example.py", line 35, in test_negative_deposit
    self.assertFalse(outcome)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)</code>
Copy after login
Copy after login

In this case, the detailed logo gives us more information. We know that test_negative_deposit fails. In particular, AssertionError tells us that the expected result should be false, but True is not false, which means that the method returns True.

unittest framework provides different assertion methods according to our needs:

  • assertEqual(x,y), test whether x == y is true
  • assertRaises(exception_type), check whether a specific exception was raised
  • assertIsNone(x), test whether x is None
  • assertIn(x,y), test whether x is in y

Now that we have a basic understanding of how to write unit tests using the unittest framework, let's take a look at another Python framework called pytest.

Introduction to pytest

pytest framework is a Python unit testing framework that has some related features:

  • It allows for complex testing with less code
  • It supports unittest test suite
  • It offers over 800 external plugins

Since pytest is not installed by default, we must first install it. Note that pytest requires Python 3.7.

Install pytest

Installing pytest is very easy. You just need to run the following command:

import unittest

class BankAccount:
  def __init__(self, id):
    self.id = id
    self.balance = 0

  def withdraw(self, amount):
    if self.balance >= amount:
      self.balance -= amount
      return True
    return False

  def deposit(self, amount):
    self.balance += amount
    return True
Copy after login
Copy after login

Then check that everything is installed correctly by typing:

class TestBankOperations(unittest.TestCase):
    def test_insufficient_deposit(self):
      # Arrange
      a = BankAccount(1)
      a.deposit(100)
      # Act
      outcome = a.withdraw(200)
      # Assert
      self.assertFalse(outcome)
Copy after login
Copy after login

The output should look like this:

python -m unittest example.py
Copy after login
Copy after login

Very good! Let's write the first test using pytest.

Writing unit tests using pytest

We will use the BankAccount class we wrote earlier and we will test the same method as before. In this way, it is easier to compare the efforts required to write tests using these two frameworks.

To use pytest for testing, we need:

  • Create a directory and put our test files into it.
  • Writing our tests in files whose names start with test_ or end with _test.py. pytest will look for these files in the current directory and its subdirectories.

So we create a file named test_bank.py and put it into a folder. This is what our first test function looks like:

<code>.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK</code>
Copy after login
Copy after login

As you have noticed, the only change compared to the unittest version is the assert part. Here we use the normal Python assertion method.

Now we can take a look at the test_bank.py file:

  def test_negative_deposit(self):
    # Arrange
    a = BankAccount(1)
    # Act
    outcome = a.deposit(-100)
    # Assert
    self.assertFalse(outcome)
Copy after login
Copy after login

To run this test, let's open a command prompt in the folder containing the test_bank.py file. Then, run the following command:

python -m unittest -v example.py
Copy after login
Copy after login

The output will look like this:

<code>test_insufficient_deposit (example.TestBankOperations) ... ok
test_negative_deposit (example.TestBankOperations) ... FAIL

======================================================================
FAIL: test_negative_deposit (example.TestBankOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "example.py", line 35, in test_negative_deposit
    self.assertFalse(outcome)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)</code>
Copy after login
Copy after login

In this case, we can see how easy it is to write and execute tests. Furthermore, we can see that we write less code than unittest. The results of the test are also easy to understand.

Let's continue to look at the failed tests!

We use the second method we wrote before, which is called test_negative_deposit. We refactor the assert part and the result is as follows:

pip install -U pytest
Copy after login

We run the tests as before, which should be the output:

pytest --version
Copy after login

By parsing the output we can read collected 2 items, which means that two tests have been performed. Scroll down and we can see an error occurred while testing the test_negative_deposit method. In particular, errors occur when evaluating assertions. In addition, the report also states that the value of the outcome variable is True, which means that the deposit method contains errors.

Since pytest uses the default Python assertion keyword, we can compare any output we get to with another variable that stores the expected result. All of this requires no special assertion methods.

Conclusion

All in this article, we introduce the basics of software testing. We discovered why software testing is crucial and why everyone should test their code. We discuss unit testing and how to design and implement simple unit testing in Python.

We use two Python frameworks called unittest and pytest. Both have useful features, and they are two of the most commonly used frameworks in Python unit testing.

Finally, we see two basic test cases to give you an idea of ​​how to write tests in preparation, execution, and assertion patterns.

I hope I have convinced you of the importance of software testing. Choose a framework, such as unittest or pytest, and start testing – because the extra effort is worth it!

If you like this article, you may also find the following useful:

  • Cypress Testing: A Guide to Running Web Applications
  • How to Test React Components with Jest
  • Learn end-to-end testing with Puppeteer
  • 3 ways to continuously test hands-free
  • Reintroduction Jenkins: Use pipes for automated testing

FAQs about Python unit testing

What are unit tests in Python? Unit testing in Python is a software testing technique in which a single unit or component of a program is tested isolated to ensure that each unit works as expected.

Why is unit testing important in Python development? Unit testing helps ensure the correctness of individual components in a Python program. It helps to detect errors early, provides a safe net for code changes, and supports maintainability of code.

How to write unit tests in Python? Unit tests in Python are often written using the built-in unittest module. You create test classes inherited from unittest.TestCase and write test methods in these classes. Test methods usually start with "test".

What other frameworks can I use for Python unit testing besides unittest? Yes, besides unittest there are other popular Python testing frameworks such as pytest and nose2. These frameworks provide different features and syntaxes, allowing developers to choose the one that best suits their needs.

What is the role of fixture in Python unit testing? Fixture is a way to set preconditions in Python and clean up after testing. They help ensure that the tests are independent and can run independently.

What is test coverage and why it is important? Test coverage measures the percentage of the code base your tests execute. It helps identify untested code and ensures that your tests are comprehensive, thus reducing the possibility of errors being discovered.

What are the best practices for writing effective unit tests in Python? Yes, some best practices include writing independent and isolated tests, using descriptive test method names, and testing boundary situations. In addition, try to get good test coverage and run tests frequently.

The above is the detailed content of A Guide to Python Unit Testing with unittest and pytest. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template