The Way of Python Unit Testing: A Comprehensive Guide from Getting Started to Mastering

In this article, we will explore all aspects of Python unit testing in depth, including its basic concepts, basic knowledge, practical methods, advanced topics, how to perform unit testing in actual projects, best practices for unit testing, and some Helpful Tools and Resources

1. The importance of unit testing

Testing is an indispensable part of software development. It can help us ensure the quality of the code, reduce bugs, and improve the stability of the system. Among various testing methods, unit testing is especially popular among developers because of its fast and effective features. This article will give a comprehensive introduction to unit testing in Python.

1.1 Why is unit testing important?

In the process of writing code, we may encounter various problems, and if these problems are not properly handled, they will often become unforeseen bugs after the project goes online. These bugs will not only affect the user experience, but may also cause serious economic losses. Therefore, unit testing is particularly important, it can help us discover and solve problems in the process of code development, and avoid the accumulation and amplification of problems.

For example, when we write a simple addition function:

def add(x, y):
    return x + y

We can ensure the functionality of this function by writing a simple unit test:

import unittest

class TestAdd(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(1, 2), 3)

By running this test, we can verify addthat the function is working correctly.

1.2 Application of unit testing in Python

Python has a built-in unittestmodule that we can use for unit testing. In addition, the Python community also provides some other unit testing tools, such as pytest, noseetc. This article will mainly introduce how to use Python unittestmodules for unit testing.

In the development process of Python, a good unit test can not only help us ensure the quality of the code, but also serve as a document to help other developers understand and use our code. Therefore, unit testing occupies a very important position in the development process of Python.

2. Basic knowledge of Python unit testing

Before introducing the specific operation of unit testing, we need to understand some basic knowledge. In this part, we will learn what unit testing is, and Python's unittest module.

2.1 What is unit testing?

Unit Testing (Unit Testing) is a software testing method whose goal is to verify whether the behavior of each independent unit (usually a function, method, or class) in the code meets our expectations. Unit testing has many advantages, such as fast, instant feedback, easy to locate problems, etc., and is an important part of test-driven development (TDD).

For example, we have a function to square a number:

def square(n):
    return n * n

We can write a unit test to verify that this function works correctly:

import unittest

class TestSquare(unittest.TestCase):
    def test_square(self):
        self.assertEqual(square(2), 4)
        self.assertEqual(square(-2), 4)
        self.assertEqual(square(0), 0)

In this way, whenever our code is modified, we can quickly check whether there is a problem by running this unit test.

2.2 Introduction to Python's unittest module

The Python unittestmodule is a module for unit testing in the Python standard library, which provides a rich set of APIs for us to write and run unit tests. unittestThe use of the module mainly includes three steps:

  1. Import unittestthe module.
  2. Define a unittest.TestCasetest class that inherits from, and then define various test methods in this class (the method name starts with test_).
  3. Run the tests on the command line.

Here is a simple example:

import unittest

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 1, 2)

    def test_subtract(self):
        self.assertEqual(3 - 2, 1)

if __name__ == '__main__':
    unittest.main()

Running this script on the command line will execute all test methods and output the test results.

3. Python unit testing practice

Now that we understand the basics of unit testing, we'll get into practice. In this part, we will demonstrate how to write and run unit tests in Python.

3.1 How to write a basic unit test?

In Python, we can use unittestmodules to write unit tests. A basic unit test usually consists of the following parts:

  1. Import unittestthe module.
  2. Define a unittest.TestCasetest class that inherits from.
  3. Various test methods (method names starting with ) are defined in this test class test_.
  4. Various assertion methods are used in these test methods unittest.TestCaseto check the behavior of the code under test.

For example, we have the following function:

def divide(x, y):
    if y == 0:
        raise ValueError("Can not divide by zero!")
    return x / y

We can write unit tests like this:

import unittest

class TestDivide(unittest.TestCase):
    def test_divide(self):
        self.assertEqual(divide(4, 2), 2)
        self.assertEqual(divide(-4, 2), -2)
        self.assertRaises(ValueError, divide, 4, 0)

if __name__ == '__main__':
    unittest.main()

In this example, we have used the unittest.TestCasemethod assertEqualand assertRaisesmethod to check dividethe behavior of the function.

3.2 Concept and creation of test cases, test suites and test runners

In unittestthe module, we have the following important concepts:

  • Test Case: A test case is a complete test process, including the preparation before the test, the execution of the test action and the cleaning after the test. In unittesta module, a test case is an unittest.TestCaseinstance of a test case.
  • Test Suite: A test suite is a collection of test cases or test suites. We can use unittest.TestSuiteclasses to create test suites.
  • Test Runner: A test runner is used to execute and control tests. We can use unittest.TextTestRunnerclasses to create a simple textual test runner.

Here is an example:

import unittest

class TestMath(unittest.TestCase):
    # 测试用例
    def test_add(self):
        self.assertEqual(1 + 1, 2)

    def test_subtract(self):
        self.assertEqual(3 - 2, 1)

# 创建测试套件
suite = unittest.TestSuite()
suite.addTest(TestMath('test_add'))
suite.addTest(TestMath('test_subtract'))

# 创建测试运行器
runner = unittest.TextTestRunner()
runner.run(suite)

In this example, we create a test suite with two test cases and then execute the test suite with a text test runner.

3.3 Use setUp and tearDown to handle preparation and cleanup before and after testing

When writing unit tests, we often need to do some preparation and cleanup before and after each test method is executed. For example, we may need to create some objects before each test method starts, and then destroy these objects after each test method ends. setUpWe can define and methods in the test class tearDownto achieve these functions.

import unittest

class TestDatabase(unittest.TestCase):
    def setUp(self):
        # 创建数据库连接
        self.conn = create_database_connection()

    def tearDown(self):
        # 关闭数据库连接
        self.conn.close()

    def test_insert(self):
        # 使用数据库连接进行测试
        self.conn.insert(...)

In this example, we setUpcreate a database connection in the method and tearDownclose the database connection in the method. In this way, we can use this database connection for testing in each test method without creating and destroying the database connection in each test method.

Fourth, Python unit testing advanced topics

We have learned the basic concepts and usage of Python unit testing. Now, we'll dive into some advanced topics, including test-driven development (TDD), mocking objects (Mocking), and parameterized testing.

4.1 Test Driven Development (TDD)

Test-Driven Development (TDD for short) is a software development methodology that emphasizes writing unit tests before writing code. The basic steps of TDD are:

  1. Write a failing unit test first.
  2. Write code so that this unit test passes.
  3. Refactor the code to make it better.

TDD helps us maintain the quality of the code, and also makes our code easier to maintain and modify.

4.2 Mocking

When writing unit tests, we sometimes need to simulate some external, uncontrollable factors, such as time, database, network requests, etc. Python unittest.mockmodules provide a way to create mock objects that we can use to simulate external, uncontrollable factors.

For example, suppose we have a function that decides what to return based on the current time:

import datetime

def get_greeting():
    current_hour = datetime.datetime.now().hour
    if current_hour < 12:
        return "Good morning!"
    elif current_hour < 18:
        return "Good afternoon!"
    else:
        return "Good evening!"

We can unittest.mockmock the current time with , in order to test this function:

import unittest
from unittest.mock import patch

class TestGreeting(unittest.TestCase):
    @patch('datetime.datetime')
    def test_get_greeting(self, mock_datetime):
        mock_datetime.now.return_value.hour = 9
        self.assertEqual(get_greeting(), "Good morning!")

        mock_datetime.now.return_value.hour = 15
        self.assertEqual(get_greeting(), "Good afternoon!")

        mock_datetime.now.return_value.hour = 20
        self.assertEqual(get_greeting(), "Good evening!")

if __name__ == '__main__':
    unittest.main()

In this example, we use unittest.mock.patchto mock datetime.datetimethe object and then set nowthe return value of its method.

4.3 Parametric testing

Parameterized testing is a unit testing technique that allows us to run the same test with different input data. In Python unittestmodules, we can use unittest.subTestcontext managers to implement parameterized tests.

Here is an example:

import unittest

class TestSquare(unittest.TestCase):
    def test_square(self):
        for i in range(-10, 11):
            with self.subTest(i=i):
                self.assertEqual(square(i), i * i)

if __name__ == '__main__':
    unittest.main()

In this example, we use unittest.subTestthe context manager to run 20 different tests, each with different input data.

5. Practical exercise: a complete project example for Python unit testing

In this part, we'll walk through a simple project to show how to apply Python unit testing in practice. We'll create a simple Fraction Calculator app that can add, subtract, multiply, and divide fractions.

5.1 Create a project

First, we create a new Python project and create a fraction_calculator.pyfile within the project. In this file, we define a Fractionclass to represent scores. This class has two properties: numerator and denominator.

# fraction_calculator.py

class Fraction:
    def __init__(self, numerator, denominator):
        if denominator == 0:
            raise ValueError("Denominator cannot be zero!")
        self.numerator = numerator
        self.denominator = denominator

5.2 Writing unit tests

Then, we create a test_fraction_calculator.pyfile where we write unit tests to test the Fractionclass.

# test_fraction_calculator.py

import unittest
from fraction_calculator import Fraction

class TestFraction(unittest.TestCase):
    def test_create_fraction(self):
        f = Fraction(1, 2)
        self.assertEqual(f.numerator, 1)
        self.assertEqual(f.denominator, 2)

    def test_create_fraction_with_zero_denominator(self):
        with self.assertRaises(ValueError):
            Fraction(1, 0)

if __name__ == '__main__':
    unittest.main()

In this test class, we create two test methods: test_create_fractionto test that fractions are created normally, and test_create_fraction_with_zero_denominatorto test that an exception should be thrown when the denominator is zero.

5.3 Executing unit tests

Finally, we run the file on the command line test_fraction_calculator.pyto execute the unit tests.

python -m unittest test_fraction_calculator.py

If all the tests pass, then we can say with confidence that our Fractionclass is correct.

5.4 Extended items

Of course, our project is far from complete. FractionThe class also needs to add many functions, such as addition, subtraction, multiplication, and division operations, reducing fractions, converting to floating point numbers, and so on. For each new function, we need to write corresponding unit tests to ensure its correctness. And, we also need to run these unit tests constantly to ensure that our modifications do not break existing functionality.

Unit testing is an ongoing process, not a one-off task. Only by constantly writing and running unit tests can we guarantee the quality and reliability of our code.

Six, the best practice of Python unit testing

In the process of actually writing and executing Python unit tests, there are some best practices that can help us improve our work efficiency and ensure the quality and reliability of our tests.

6.1 Always write the tests first

According to the principle of test-driven development (TDD), we should first write the test, and then write the code that can pass the test. This can help us understand the functions we want to achieve more clearly, and also ensure that our code is testable.

6.2 Keep tests independent

Each test should be independent and not depend on other tests. If there are dependencies between tests, a failure of one test may cause other tests to fail as well, which makes the test results hard to understand and makes the tests harder to maintain.

6.3 Test all possible cases

We should try to test all possible cases, including normal cases, edge cases and abnormal cases. For example, if we have a function that takes an integer between 0 and 100 as an argument, then we should test how this function behaves with arguments of 0, 50, 100, and other values.

6.4 Using mock objects

When testing code involving external systems (such as databases, network services, etc.), we can use mock objects (Mocking) instead of real external systems. This makes tests faster, more stable, and more controllable.

6.5 Regularly run tests

We should run our tests regularly to make sure our code isn't broken. A common practice is to run tests before every code commit. In addition, we can also use continuous integration (Continuous Integration) tools, such as Jenkins, Travis CI, etc., to automatically run our tests.

6.6 Using code coverage tools

Code coverage is a metric used to indicate how much code our tests cover. We can use code coverage tools, such as coverage.py, to measure our code coverage and work hard to improve this metric. However, keep in mind that code coverage does not guarantee the quality and completeness of our tests. It's just a tool, we can't rely too much on it.

# 运行代码覆盖率工具的示例
# 在命令行中输入以下命令:

$ coverage run --source=. -m unittest discover
$ coverage report

The above command will first run all your unit tests and collect code coverage information. It will then display a code coverage report which will tell you which code is covered by the tests and which is not.

7. Tools and resources

There are some tools and resources that can help us improve efficiency and quality when doing Python unit testing.

7.1 Python's built-in unittest module

Python's built-in unittest module is a powerful unit testing framework that provides rich assertion methods, test suites, and test runners. If you want to do unit testing, the unittest module is a great place to start.

# unittest模块的基本使用
import unittest

class TestMyFunction(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(1, 2), 3)

if __name__ == '__main__':
    unittest.main()

7.2 pytest

pytest is a popular Python testing framework that is more compact and powerful than unittest. It can be used not only for unit testing, but also for functional testing, integration testing, etc.

# pytest的基本使用
def test_add():
    assert add(1, 2) == 3

7.3 mock

The mock module helps you create mock objects to substitute for real objects in tests. This is useful for testing code that depends on external systems or objects that are difficult to construct.

# mock模块的基本使用
from unittest.mock import Mock

# 创建一个模拟对象
mock = Mock()
# 设置模拟对象的返回值
mock.return_value = 42
# 使用模拟对象
assert mock() == 42

7.4 coverage.py

coverage.py is a code coverage tool that helps you find out which code is not covered by tests.

# coverage.py的基本使用
coverage run --source=. -m unittest discover
coverage report

7.5 Python Testing

Python Testing is a website about Python testing that provides many tutorials, tools, books, and other resources on Python testing. The URL is: http://pythontesting.net

8. Summary

I hope that through this article, you have a deeper understanding and application of Python unit testing. Unit testing is a very important part of the software development process. Correct unit testing can help us improve code quality, find and fix problems, and improve development efficiency. Python provides a series of powerful tools for unit testing, and these tools can help us write better unit tests.

In the process of writing unit tests, we can not only find and fix problems, but also deeply understand our code and business logic, and improve our programming skills.

If it is helpful, please pay more attention to the personal WeChat public account: [Python full perspective] TeahLead_KrisChang, 10+ years of experience in the Internet and artificial intelligence industry, 10+ years of experience in technology and business team management, Tongji Software Engineering Bachelor, Fudan Engineering Management Master, Aliyun certified cloud service senior architect, head of AI product business with hundreds of millions of revenue.

Guess you like

Origin blog.csdn.net/magicyangjay111/article/details/131783547