杰瑞科技汇

Python单元测试如何高效编写与执行?

Of course! Here is a comprehensive guide to unit testing in Python, covering the fundamentals, best practices, and a complete walkthrough with examples.

Python单元测试如何高效编写与执行?-图1
(图片来源网络,侵删)

What is Unit Testing?

At its core, unit testing is the practice of writing small, isolated tests for individual units of your code (like a single function or method). The goal is to verify that each unit works correctly in isolation, independent of the rest of the application.

Why is it so important?

  • Catch Bugs Early: Tests act as a safety net, catching errors before they make it into production.
  • Enable Refactoring: With a solid test suite, you can confidently change your code, knowing that if you break something, the tests will immediately tell you.
  • Live Documentation: Tests demonstrate how your code is supposed to be used and what its expected behavior is.
  • Design Improvement: The process of writing tests often forces you to write cleaner, more modular, and more testable code.

Python's Built-in Testing Framework: unittest

Python comes with a built-in module called unittest, which provides a rich set of tools for writing and running tests. It's based on the popular Java JUnit framework.

The key components of unittest are:

Python单元测试如何高效编写与执行?-图2
(图片来源网络,侵删)
  1. unittest.TestCase: The base class for creating test cases. You inherit from this class to write your individual tests.
  2. Assertions: Methods on the TestCase object that check if a condition is true (e.g., assertEqual(), assertTrue()). If the condition is false, the test fails.
  3. Test Suite: A collection of test cases that you can run together.
  4. Test Runner: The component that executes the tests and reports the results.

Step-by-Step Guide to Writing Unit Tests

Let's build a simple module and then write tests for it.

Step 1: The Code to Be Tested (calculator.py)

Imagine you have a simple calculator module.

# calculator.py
def add(a, b):
    """Adds two numbers and returns the result."""
    return a + b
def subtract(a, b):
    """Subtracts b from a and returns the result."""
    return a - b
def multiply(a, b):
    """Multiplies two numbers and returns the result."""
    return a * b
def divide(a, b):
    """Divides a by b and returns the result."""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

Step 2: Creating the Test File (test_calculator.py)

Now, create a new file for your tests. It's a common convention to name your test files test_*.py or *_test.py.

In this file, you'll:

Python单元测试如何高效编写与执行?-图3
(图片来源网络,侵删)
  1. Import the unittest module.
  2. Import the code you want to test (calculator).
  3. Create a class that inherits from unittest.TestCase.
  4. Write methods inside this class, where each method is a single test case. The method names must start with test_.
# test_calculator.py
import unittest
from calculator import add, subtract, multiply, divide
class TestCalculator(unittest.TestCase):
    """Test suite for the calculator module."""
    # --- Tests for the 'add' function ---
    def test_add_positive_numbers(self):
        """Test addition of two positive numbers."""
        self.assertEqual(add(2, 3), 5)
    def test_add_negative_numbers(self):
        """Test addition of two negative numbers."""
        self.assertEqual(add(-2, -3), -5)
    def test_add_with_zero(self):
        """Test addition with zero."""
        self.assertEqual(add(5, 0), 5)
    # --- Tests for the 'subtract' function ---
    def test_subtract(self):
        """Test basic subtraction."""
        self.assertEqual(subtract(10, 4), 6)
    # --- Tests for the 'multiply' function ---
    def test_multiply(self):
        """Test basic multiplication."""
        self.assertEqual(multiply(3, 7), 21)
    # --- Tests for the 'divide' function ---
    def test_divide(self):
        """Test basic division."""
        self.assertEqual(divide(10, 2), 5)
    def test_divide_by_zero(self):
        """Test that division by zero raises a ValueError."""
        with self.assertRaises(ValueError):
            divide(10, 0)
if __name__ == '__main__':
    unittest.main()

Step 3: Running the Tests

You can run your tests in several ways.

Method 1: From the Command Line (Recommended)

Navigate to the directory containing your files in your terminal and run:

# Discovers and runs all tests in the current directory
python -m unittest discover

Or, to run a specific test file:

# Runs all tests within the test_calculator.py file
python -m unittest test_calculator.py

Or, to run a specific test class or method:

# Runs a specific test class
python -m unittest test_calculator.TestCalculator
# Runs a specific test method
python -m unittest test_calculator.TestCalculator.test_add_positive_numbers

Method 2: From Within the Script

The if __name__ == '__main__': block in test_calculator.py allows you to run the tests directly by executing the script:

python test_calculator.py

Expected Output

If all tests pass, you'll see something like this:

.......
----------------------------------------------------------------------
Ran 7 tests in 0.001s
OK

The dots () represent each passing test.

If a test fails, the output will be more detailed, telling you which test failed and why. For example, if we had a typo in test_add_positive_numbers (self.assertEqual(add(2, 3), 6)), the output would be:

F.......
======================================================================
FAIL: test_add_positive_numbers (__main__.TestCalculator)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_calculator.py", line 10, in test_add_positive_numbers
    self.assertEqual(add(2, 3), 6)
AssertionError: 5 != 6
----------------------------------------------------------------------
Ran 7 tests in 0.001s
FAILED (failures=1)

Key unittest Features and Best Practices

setUp() and tearDown() Methods

These special methods are run before and after each test method in your class.

  • setUp(): Use this to create objects or set up conditions that your tests need. This avoids repeating the same setup code in every test method.
  • tearDown(): Use this to clean up after a test has run (e.g., closing files, database connections).

Example:

class TestStringMethods(unittest.TestCase):
    def setUp(self):
        """Set up a list for every test."""
        self.my_list = [1, 2, 3, 4, 5]
        print("\n[Running setUp]")
    def tearDown(self):
        """Clean up after each test."""
        self.my_list = None
        print("[Running tearDown]")
    def test_list_length(self):
        self.assertEqual(len(self.my_list), 5)
    def test_list_pop(self):
        self.my_list.pop()
        self.assertEqual(len(self.my_list), 4)

Different Types of Assertions

unittest.TestCase provides a wide range of assertions.

Assertion Checks if...
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) x is true
assertFalse(x) x is false
assertIs(a, b) a is b (object identity)
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a is in b (for sequences, dicts, etc.)
assertNotIn(a, b) a is not in b
assertIsInstance(a, b) a is an instance of class b
assertNotIsInstance(a, b) a is not an instance of class b
assertRaises(Exception, func, *args, **kwargs) Calling func raises Exception

Testing for Exceptions (assertRaises)

As seen in the divide example, assertRaises is the correct way to test that your code handles errors properly.

The modern context manager syntax is very clean:

with self.assertRaises(ValueError):
    divide(10, 0)

Alternative Testing Frameworks

While unittest is great, the Python community has other popular choices.

pytest

pytest is a third-party library known for its simplicity and powerful features. It's often preferred for its cleaner syntax and less boilerplate code.

Why you might prefer pytest:

  • No need for classes: You can write simple functions as tests.
  • Simpler assertions: Just use standard Python assert statements. pytest handles the rest.
  • Rich plugin ecosystem.
  • Excellent fixtures (a more powerful version of setUp/tearDown).

pytest Example:

Rewriting our test_calculator.py with pytest:

# test_calculator_pytest.py
import pytest
from calculator import add, divide
def test_add():
    assert add(2, 3) == 5
    assert add(-1, 5) == 4
    assert add(0, 0) == 0
def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(10, 0)
# You can even run unittest tests with pytest!
# So you can adopt it gradually.

To use pytest, you first need to install it: pip install pytest. Then, you just run pytest in your terminal, and it will automatically discover and run your tests.

doctest

doctest is a unique tool that searches for pieces of text that look like interactive Python sessions in your docstrings and runs them as tests.

Example:

# calculator.py
def add(a, b):
    """
    Adds two numbers and returns the result.
    >>> add(2, 3)
    5
    >>> add(-1, 1)
    0
    >>> add('a', 'b')
    'ab'
    """
    return a + b
if __name__ == "__main__":
    import doctest
    doctest.testmod()

You run this from the command line with python calculator.py. It's great for simple, example-based tests but not for complex testing scenarios.

Summary and Best Practices

Feature unittest (Built-in) pytest (3rd Party)
Setup Included with Python pip install pytest
Test Function Must be a method in a TestCase class Can be a simple function
Assertion Special methods: self.assertEqual() Standard assert statement
Fixture Setup setUp() and tearDown() methods @pytest.fixture decorator
Discoverability Good Excellent
Community Standard library, widely used Very popular, huge ecosystem

Final Recommendations:

  • For Beginners: Start with unittest. It's built-in and teaches the fundamental concepts of unit testing.
  • For Most Projects: Use pytest. It's more developer-friendly, less verbose, and makes writing tests a more pleasant experience. It's the de-facto standard in many modern Python projects.
  • Write Tests First (TDD): Try to write your tests before you write the code they are testing (Test-Driven Development). This helps clarify requirements and leads to better design.
  • Keep Tests Fast and Isolated: Each test should be independent and run quickly. Avoid relying on external resources like live databases or network calls in your unit tests (use "mocking" for this, which is a topic for another day).
分享:
扫描分享到社交APP
上一篇
下一篇