Python案例如何实现单元测试?

wen python案例 13

Python单元测试实现指南

基本单元测试结构

# calculator.py - 待测试的模块
class Calculator:
    def add(self, a, b):
        return a + b
    def subtract(self, a, b):
        return a - b
    def multiply(self, a, b):
        return a * b
    def divide(self, a, b):
        if b == 0:
            raise ValueError("除数不能为0")
        return a / b

使用unittest框架

# test_calculator.py
import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
    def setUp(self):
        """每个测试方法执行前都会调用"""
        self.calc = Calculator()
    def test_add(self):
        """测试加法"""
        result = self.calc.add(3, 5)
        self.assertEqual(result, 8)
        self.assertEqual(self.calc.add(-1, 1), 0)
        self.assertEqual(self.calc.add(0, 0), 0)
    def test_subtract(self):
        """测试减法"""
        self.assertEqual(self.calc.subtract(10, 5), 5)
        self.assertEqual(self.calc.subtract(5, 10), -5)
    def test_multiply(self):
        """测试乘法"""
        self.assertEqual(self.calc.multiply(3, 4), 12)
        self.assertEqual(self.calc.multiply(-2, 3), -6)
        self.assertEqual(self.calc.multiply(0, 5), 0)
    def test_divide(self):
        """测试除法"""
        self.assertEqual(self.calc.divide(10, 2), 5)
        self.assertEqual(self.calc.divide(7, 2), 3.5)
        with self.assertRaises(ValueError):
            self.calc.divide(10, 0)
    def test_divide_by_zero(self):
        """测试除以零异常"""
        with self.assertRaises(ValueError) as context:
            self.calc.divide(10, 0)
        self.assertEqual(str(context.exception), "除数不能为0")
if __name__ == '__main__':
    unittest.main()

使用pytest框架(推荐)

# test_calculator_pytest.py
import pytest
from calculator import Calculator
@pytest.fixture
def calculator():
    """fixture提供测试环境"""
    return Calculator()
def test_add(calculator):
    assert calculator.add(3, 5) == 8
    assert calculator.add(-1, 1) == 0
    assert calculator.add(0, 0) == 0
def test_subtract(calculator):
    assert calculator.subtract(10, 5) == 5
    assert calculator.subtract(5, 10) == -5
def test_multiply(calculator):
    assert calculator.multiply(3, 4) == 12
    assert calculator.multiply(-2, 3) == -6
    assert calculator.multiply(0, 5) == 0
def test_divide(calculator):
    assert calculator.divide(10, 2) == 5
    assert calculator.divide(7, 2) == 3.5
def test_divide_by_zero(calculator):
    with pytest.raises(ValueError, match="除数不能为0"):
        calculator.divide(10, 0)

测试复杂场景

# user_service.py
class UserService:
    def __init__(self, database):
        self.database = database
    def create_user(self, username, email):
        if not username or not email:
            raise ValueError("用户名和邮箱不能为空")
        if self.database.exists(username):
            raise ValueError("用户已存在")
        user = {
            'username': username,
            'email': email,
            'active': True
        }
        return self.database.save(user)
    def get_user(self, username):
        user = self.database.find(username)
        if not user:
            return None
        return user
    def deactivate_user(self, username):
        user = self.get_user(username)
        if not user:
            raise ValueError("用户不存在")
        user['active'] = False
        return self.database.update(user)
# test_user_service.py
import unittest
from unittest.mock import Mock, patch
from user_service import UserService
class TestUserService(unittest.TestCase):
    def setUp(self):
        self.mock_database = Mock()
        self.service = UserService(self.mock_database)
    def test_create_user_success(self):
        """测试成功创建用户"""
        self.mock_database.exists.return_value = False
        self.mock_database.save.return_value = {
            'username': 'testuser',
            'email': 'test@example.com',
            'active': True
        }
        result = self.service.create_user('testuser', 'test@example.com')
        self.assertEqual(result['username'], 'testuser')
        self.assertTrue(result['active'])
        self.mock_database.exists.assert_called_once_with('testuser')
        self.mock_database.save.assert_called_once()
    def test_create_user_exists(self):
        """测试创建已存在的用户"""
        self.mock_database.exists.return_value = True
        with self.assertRaises(ValueError) as context:
            self.service.create_user('existing', 'test@example.com')
        self.assertEqual(str(context.exception), "用户已存在")
    def test_create_user_empty_input(self):
        """测试空输入"""
        with self.assertRaises(ValueError):
            self.service.create_user('', 'test@example.com')
        with self.assertRaises(ValueError):
            self.service.create_user('testuser', '')
    def test_get_user_found(self):
        """测试获取存在的用户"""
        expected_user = {'username': 'testuser', 'email': 'test@example.com'}
        self.mock_database.find.return_value = expected_user
        result = self.service.get_user('testuser')
        self.assertEqual(result, expected_user)
        self.mock_database.find.assert_called_once_with('testuser')
    def test_get_user_not_found(self):
        """测试获取不存在的用户"""
        self.mock_database.find.return_value = None
        result = self.service.get_user('nonexistent')
        self.assertIsNone(result)
    def test_deactivate_user(self):
        """测试停用用户"""
        user = {'username': 'testuser', 'active': True}
        self.mock_database.find.return_value = user
        self.mock_database.update.return_value = {**user, 'active': False}
        result = self.service.deactivate_user('testuser')
        self.assertFalse(result['active'])
        self.mock_database.update.assert_called_once()
if __name__ == '__main__':
    unittest.main()

运行测试

# 运行所有测试
python -m unittest discover
# 运行特定测试文件
python -m unittest test_calculator.py
# 运行特定测试类
python -m unittest test_calculator.TestCalculator
# 运行特定测试方法
python -m unittest test_calculator.TestCalculator.test_add
# 使用 pytest 运行
pytest test_calculator_pytest.py -v
# 生成覆盖率报告
pip install coverage
coverage run -m pytest
coverage report -m

实用的断言方法

import unittest
class TestAssertions(unittest.TestCase):
    def test_common_assertions(self):
        # 相等/不等
        self.assertEqual(1 + 1, 2)
        self.assertNotEqual(1 + 1, 3)
        # 布尔值
        self.assertTrue(True)
        self.assertFalse(False)
        # 空值
        self.assertIsNone(None)
        self.assertIsNotNone("hello")
        # 类型
        self.assertIsInstance(42, int)
        self.assertNotIsInstance("42", int)
        # 列表/元组
        self.assertIn("a", ["a", "b", "c"])
        self.assertNotIn("x", ["a", "b", "c"])
        # 异常
        with self.assertRaises(ValueError):
            int("abc")
        # 近似相等(浮点数)
        self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)

测试最佳实践

# test_best_practices.py
"""
最佳实践示例:
1. 测试方法命名规范:test_{function_name}_{scenario}
2. 每个测试方法只测试一个功能点
3. 使用 setUp 和 tearDown 管理测试资源
4. 测试边界条件
5. 使用 mock 隔离外部依赖
"""
import unittest
from unittest.mock import patch, MagicMock
import requests
class DataProcessor:
    def fetch_and_process(self, url):
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            return self._process(data)
        return None
    def _process(self, data):
        return [item['name'] for item in data if item.get('active')]
class TestDataProcessor(unittest.TestCase):
    @patch('requests.get')
    def test_fetch_and_process_success(self, mock_get):
        """测试成功获取并处理数据"""
        # 准备 mock 数据
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {'name': 'item1', 'active': True},
            {'name': 'item2', 'active': False},
            {'name': 'item3', 'active': True}
        ]
        mock_get.return_value = mock_response
        # 执行测试
        processor = DataProcessor()
        result = processor.fetch_and_process('http://example.com/api')
        # 验证结果
        self.assertEqual(result, ['item1', 'item3'])
        mock_get.assert_called_once_with('http://example.com/api')
    @patch('requests.get')
    def test_fetch_and_process_failure(self, mock_get):
        """测试请求失败"""
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_get.return_value = mock_response
        processor = DataProcessor()
        result = processor.fetch_and_process('http://example.com/api')
        self.assertIsNone(result)
if __name__ == '__main__':
    unittest.main()
  1. 选择测试框架unittest(内置)或 pytest(更简洁)
  2. 测试结构:遵循 AAA 模式(Arrange-Act-Assert)
  3. 命名规范test_功能_场景
  4. 隔离测试:使用 mock 对象模拟外部依赖
  5. 覆盖率:关注代码覆盖率和边界情况
  6. 持续集成:将测试集成到 CI/CD 流程中

Python案例如何实现单元测试?

抱歉,评论功能暂时关闭!