LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

30天学会Python编程:29.Python单元测试

admin
2025年7月17日 21:57 本文热度 13

1. 单元测试概念

1.1 什么是单元测试

单元测试是针对软件中最小可测试单元的验证过程。在Python中,通常指对函数、类或方法的测试。单元测试具有以下特征:

  • 隔离性:测试独立运行,不依赖外部资源
  • 自动化:可自动执行和验证结果
  • 快速反馈:执行速度快,提供即时反馈
  • 可重复:每次执行结果一致


1.2 测试金字塔原则


金字塔原则要点

  • 单元测试应占测试套件的主体(70%)
  • 集成测试验证模块间交互(20%)
  • UI/端到端测试验证整体功能(10%)
  • 高层测试失败时,应补充底层测试

1.3 单元测试核心组件

组件
描述
示例
测试用例(TestCase)
测试的最小单位
test_addition()
测试固件(Fixture)
测试前的准备和清理
setUp()
tearDown()
断言(Assertion)
验证预期结果
assertEqual(result, 5)
测试套件(TestSuite)
测试用例集合
suite.addTest(TestMath)
测试运行器(TestRunner)
执行测试并报告
unittest.TextTestRunner()

2. unittest框架简介

2.1 基本测试结构

import unittest

classMathOperations:
    defadd(self, a, b):
        return a + b

classTestMathOperations(unittest.TestCase):
    defsetUp(self):
        """每个测试方法前执行"""
        self.math = MathOperations()
    
    deftest_add_integers(self):
        """测试整数加法"""
        result = self.math.add(53)
        self.assertEqual(result, 8)
        
    deftest_add_floats(self):
        """测试浮点数加法"""
        result = self.math.add(2.53.1)
        self.assertAlmostEqual(result, 5.6, places=1)
    
    deftearDown(self):
        """每个测试方法后执行"""
        delself.math

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

2.2 常用断言方法

断言方法
检查条件
示例
assertEqual(a, b)
a == b
self.assertEqual(sum([1,2,3]), 6)
assertNotEqual(a, b)
a != b
self.assertNotEqual('a', 'b')
assertTrue(x)
bool(x) is True
self.assertTrue(10 > 5)
assertFalse(x)
bool(x) is False
self.assertFalse(5 > 10)
assertIs(a, b)
a is b
self.assertIs(obj1, obj2)
assertIsNone(x)
x is None
self.assertIsNone(result)
assertIn(a, b)
a in b
self.assertIn(3, [1,2,3])
assertRaises(exc)
引发指定异常
with self.assertRaises(ValueError):
assertAlmostEqual(a, b)
约等于(浮点数)
self.assertAlmostEqual(0.1+0.2, 0.3, places=7)

2.3 测试固件用法进阶

class DatabaseTest(unittest.TestCase):
    @classmethod
    defsetUpClass(cls):
        """整个测试类执行前调用一次"""
        cls.db = create_test_database()
        cls.db.connect()
    
    defsetUp(self):
        """每个测试方法前调用"""
        self.cursor = self.db.create_cursor()
        self.cursor.execute("BEGIN TRANSACTION")
    
    deftest_insert(self):
        self.cursor.execute("INSERT INTO users VALUES ('Alice')")
        # 验证插入操作...
    
    deftearDown(self):
        """每个测试方法后调用"""
        self.cursor.execute("ROLLBACK")
        self.cursor.close()
    
    @classmethod
    deftearDownClass(cls):
        """整个测试类执行后调用一次"""
        cls.db.disconnect()
        cls.db.drop()

3. pytest框架应用示例

3.1 pytest核心优势

  • 无需继承测试类
  • 自动发现测试
  • 丰富的插件生态系统
  • 更简洁的断言语法
  • 参数化测试支持

3.2 基本测试示例

# content of test_math.py
defadd(a, b):
    return a + b

deftest_add_integers():
    assert add(53) == 8

deftest_add_floats():
    """测试浮点数加法"""
    result = add(2.53.1)
    assertabs(result - 5.6) < 0.001

deftest_add_strings():
    """测试字符串拼接"""
    assert add("Hello""World") == "HelloWorld"

3.3 应用进阶

import pytest

@pytest.fixture(scope="module")
defdatabase():
    """模块级数据库固件"""
    db = create_test_db()
    yield db  # 测试执行后继续执行清理
    db.drop()

@pytest.mark.parametrize("a,b,expected", [
    (235),
    (000),
    (-550),
    (100200300),
])

deftest_addition(a, b, expected):
    assert add(a, b) == expected

@pytest.mark.slow
deftest_large_number_addition():
    """标记为慢测试"""
    assert add(10**181) == 10**18 + 1

@pytest.mark.skipif(sys.version_info < (38), 
                   reason="需要Python 3.8或更高版本")

deftest_walrus_operator():
    """跳过特定Python版本的测试"""
    assert (result := add(34)) == 7

4. 测试替身技术

4.1 测试替身类型对比

类型
目的
适用场景
Mock
模拟对象行为
替换外部服务调用
Stub
提供预设响应
固定数据返回
Spy
记录调用信息
验证函数调用参数
Fake
简化功能实现
内存数据库替代

4.2 unittest.mock应用举例

from unittest.mock import MagicMock, patch, PropertyMock

classPaymentProcessor:
    defprocess_payment(self, amount, card_number):
        # 实际实现调用外部支付网关
        pass

classTestOrder(unittest.TestCase):
    @patch('payment.PaymentProcessor.process_payment')
    deftest_order_payment(self, mock_process):
        """测试订单支付流程"""
        order = Order(total=100)
        order.pay("4111111111111111")
        
        # 验证支付方法被调用
        mock_process.assert_called_once_with(100"4111111111111111")
    
    deftest_inventory_update(self):
        """测试库存更新"""
        with patch('inventory.Inventory.decrease'as mock_decrease:
            order = Order(items=[{"id"1"quantity"2}])
            order.process()
            
            # 验证库存更新方法被调用
            mock_decrease.assert_called_once_with(12)
    
    @patch.object(Product, 'price', new_callable=PropertyMock)
    deftest_discount_calculation(self, mock_price):
        """测试折扣计算逻辑"""
        mock_price.return_value = 100
        product = Product(id=1)
        
        # 测试折扣逻辑
        discount = calculate_discount(product, 20)
        self.assertEqual(discount, 20)

5. 参数化与数据驱动测试

5.1 unittest参数化实现

import unittest
from parameterized import parameterized

class TestMath(unittest.TestCase):
    @parameterized.expand([
        ("positive"538),
        ("zero"000),
        ("negative", -550),
        ("large_numbers"10**610**62*10**6),
    ])

    def test_add(self, name, a, b, expected):
        result = add(a, b)
        self.assertEqual(result, expected)

5.2 pytest参数化实践

import pytest
import csv

defload_test_data():
    """从CSV文件加载测试数据"""
    withopen('test_data.csv''r'as f:
        reader = csv.DictReader(f)
        return [tuple(row.values()) for row in reader]

@pytest.mark.parametrize("a,b,expected", [
    (235),
    (000),
    pytest.param(-550id="negative_and_positive"),
    pytest.param(100200300, marks=pytest.mark.slow),
])

deftest_addition(a, b, expected):
    assert add(a, b) == expected

@pytest.mark.parametrize("a,b,expected", load_test_data())
deftest_from_csv(a, b, expected):
    """从外部数据源加载测试用例"""
    a = int(a)
    b = int(b)
    expected = int(expected)
    assert add(a, b) == expected

6. 测试覆盖率分析

6.1 覆盖率类型

覆盖率类型
测量内容
重要性
语句覆盖率
代码行是否执行
基础指标
分支覆盖率
条件分支是否覆盖
关键指标
函数覆盖率
函数是否被调用
中等
行覆盖率
代码行执行情况
基础
条件覆盖率
布尔子表达式
高级

6.2 使用pytest-cov

# 安装插件
pip install pytest-cov

# 运行测试并生成报告
pytest --cov=my_project --cov-report=html

# 检查最小覆盖率
pytest --cov=my_project --cov-fail-under=90

6.3 覆盖率配置示例

# .coveragerc 配置文件
[run]
source = my_project
branch = True# 启用分支覆盖
omit = 
    */__init__.py
    */tests/*

[report]
show_missing = True# 显示未覆盖行
exclude_lines = 
    pragma: no cover
    def __repr__
    if __name__ == .__main__.:

7. 测试驱动开发(TDD)

7.1 TDD循环


7.2 TDD实战示例

# 步骤1:编写失败测试
deftest_fibonacci():
    assert fibonacci(0) == 0
    assert fibonacci(1) == 1
    assert fibonacci(2) == 1
    assert fibonacci(5) == 5

# 步骤2:实现最小功能
deffibonacci(n):
    if n == 0
        return0
    elif n == 1:
        return1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

# 步骤3:重构改进(添加缓存优化)
from functools import lru_cache

@lru_cache(maxsize=None)
deffibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

8. 实践技巧

8.1 测试金字塔


8.2 测试优化策略

1. 并行测试:使用pytest-xdist并行执行

pytest -n auto  # 自动检测CPU核心数

2. 测试标记:区分快慢测试

@pytest.mark.slow
def test_large_data_processing():
    # 慢测试...

3. 测试数据管理:使用工厂模式创建测试数据

def user_factory(**kwargs):
    defaults = {"name""Test""email""test@example.com"}
    return {**defaults, **kwargs}

4. 避免测试脆弱性:

    • 不依赖固定时间
    • 不依赖随机数
    • 不依赖外部服务
    • 不依赖执行顺序

8.3 测试反模式

反模式
问题
改进方案
上帝测试
单个测试做太多事
拆分多个单一职责测试
测试耦合
测试间依赖关系
确保测试完全独立
实现细节测试
测试内部实现而非行为
测试公共接口行为
不稳定测试
时好时坏
移除时间/随机依赖
慢测试
执行速度慢
使用Mock或优化代码

9. 综合应用示例

9.1 Web应用测试

from fastapi.testclient import TestClient
from myapp.main import app

client = TestClient(app)

deftest_create_user():
    response = client.post(
        "/users/",
        json={"name""Alice""email""alice@example.com"}
    )
    assert response.status_code == 201
    assert response.json()["name"] == "Alice"
    
    # 验证用户是否创建成功
    user_id = response.json()["id"]
    get_response = client.get(f"/users/{user_id}")
    assert get_response.status_code == 200
    assert get_response.json()["email"] == "alice@example.com"

@patch("myapp.services.send_welcome_email")
deftest_user_creation_sends_email(mock_send):
    client.post("/users/", json={"name""Bob""email""bob@example.com"})
    mock_send.assert_called_once_with("bob@example.com")

9.2 数据库应用测试

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

@pytest.fixture(scope="module")
deftest_db():
    # 内存数据库用于测试
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    yield Session()
    # 测试后自动清理
    Base.metadata.drop_all(engine)

deftest_create_user(test_db):
    user = User(name="Alice", email="alice@example.com")
    test_db.add(user)
    test_db.commit()
    
    saved_user = test_db.query(User).filter_by(email="alice@example.com").first()
    assert saved_user isnotNone
    assert saved_user.name == "Alice"
    
    # 测试唯一约束
    duplicate = User(name="Alice2", email="alice@example.com")
    test_db.add(duplicate)
    with pytest.raises(IntegrityError):
        test_db.commit()

10. 持续学习路线

11. 高级实践

11.1 测试命名规范

  • 方法命名:test_<功能>_<条件>_<预期结果>
    def test_add_positive_numbers_returns_sum()
    def test_login_with_invalid_credentials_raises_error()
  • 类命名:Test<模块名>
    class TestUserModel(unittest.TestCase):

11.2 测试组织策略

project/
├── src/
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
└── tests/
    ├── unit/
    │   ├── test_module1.py
    │   └── test_module2.py
    ├── integration/
    │   └── test_integration.py
    └── e2e/
        └── test_e2e.py

11.3 测试性能优化

  1. 使用事务回滚:避免数据库实际写入
  2. 内存数据库:SQLite替代生产数据库
  3. Mock外部服务:避免网络延迟
  4. 共享固件:减少重复初始化
  5. 并行执行:利用多核CPU

11.4 测试驱动开发原则

  1. 先写测试,再写实现
  2. 只编写刚好让测试通过的代码
  3. 保持测试快速运行(<10分钟)
  4. 测试失败时立即修复
  5. 定期重构测试代码

12. 总结

单元测试是Python开发中的核心实践,提供以下关键价值:

  • 质量保证:提前发现代码缺陷
  • 设计指导:促进模块化、低耦合设计
  • 文档作用:展示代码使用方式和预期行为
  • 重构安全网:支持代码演进而不破坏功能
  • 快速反馈:加速开发迭代流程

高级进阶方向

  • 学习使用tox进行多环境测试
  • 探索属性测试(hypothesis)
  • 实践突变测试(mutmut)
  • 研究测试容器(testcontainers)
  • 掌握持续集成中的测试优化

"没有测试的代码就是坏代码,不管它写得多么优雅" - Martin Fowler

通过持续实践单元测试,我们能构建更健壮、可维护的Python应用程序,显著提高开发效率和代码质量。


阅读原文:原文链接


该文章在 2025/7/18 10:44:27 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved