Python 单元测试详解

简介: 单元测试是Python开发中不可或缺的环节,能确保代码按预期运行、发现Bug、提升代码质量并支持安全重构。本文从基础概念讲起,逐步介绍Python单元测试的实践方法,涵盖unittest框架、pytest框架、断言使用、Mock技巧及测试覆盖率分析,助你全面掌握单元测试技能。

单元测试是写代码时不可或缺的一部分,尤其是用 Python 开发的时候。它能帮你确保代码按预期工作,发现 bug,还能让代码重构更安心。这篇文章会带你一步步搞懂 Python 单元测试,从基本概念到实际操作。


参考文章:Python 单元测试 | 简单一点学习 easyeasy.me


1. 什么是单元测试?

单元测试就是针对代码中最小的功能单元(通常是一个函数或方法)进行测试,确保它在各种输入下都能按预期工作。简单说,就是写点小测试,验证你的代码是不是靠谱。

  • 为什么要做单元测试?
  • 发现 bug:早点找到问题,改起来省心。
  • 提高代码质量:写测试逼着你把代码写得更模块化。
  • 便于重构:有测试兜底,改代码不怕翻车。
  • 文档化:测试用例能让人快速看懂代码的功能。
  • 单元测试 vs 其他测试
  • 单元测试:只测单个函数或方法,隔离外部依赖(比如数据库、文件)。
  • 集成测试:测多个组件一起工作的情况。
  • 系统测试:测整个系统,接近真实使用场景。

接下来,我们从最简单的 Python 内置测试框架 unittest 开始。

2. Python 的 unittest 框架入门

Python 自带了一个叫 unittest 的模块,简单好用,适合写单元测试。我们先来看一个最基础的例子,假设你有个计算平方数的函数:

# math_utils.py
def square(num):
    return num * num

怎么给这个函数写单元测试呢?下面是一个简单的测试代码:

# test_math_utils.py
import unittest
from math_utils import square
class TestSquare(unittest.TestCase):
    def test_positive_number(self):
        result = square(3)
        self.assertEqual(result, 9)  # 验证 3 的平方是 9
    def test_negative_number(self):
        result = square(-2)
        self.assertEqual(result, 4)  # 验证 -2 的平方是 4
    def test_zero(self):
        result = square(0)
        self.assertEqual(result, 0)  # 验证 0 的平方是 0
if __name__ == '__main__':
    unittest.main()

代码解析

  • 导入 unittest:这是 Python 内置的测试框架。
  • 测试类:继承 unittest.TestCase,每个测试类包含多个测试用例。
  • 测试方法:以 test_ 开头的方法是测试用例,比如 test_positive_number
  • 断言:用 self.assertEqual 检查结果是否符合预期。
  • 运行测试unittest.main() 会自动运行所有测试用例。

运行测试

保存上面两个文件后,在命令行运行:

python -m unittest test_math_utils.py

输出会告诉你测试是否通过。如果通过,会看到类似:

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

如果有测试失败,会明确告诉你哪个测试用例出了问题。

3. 常用断言方法

unittest 提供了很多断言方法,帮你验证各种情况。以下是常用的几个:

断言方法 作用
assertEqual(a, b) 检查 a == b
assertNotEqual(a, b) 检查 a != b
assertTrue(x) 检查 xTrue
assertFalse(x) 检查 xFalse
assertIs(a, b) 检查 a is b
assertIsNone(x) 检查 x is None
assertIn(a, b) 检查 ab
assertRaises(Exception) 检查代码是否抛出指定异常

断言示例

假设我们有个函数会抛出异常:

# math_utils.py
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

对应的测试代码:

# test_math_utils.py
import unittest
from math_utils import divide
class TestDivide(unittest.TestCase):
    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            divide(10, 0)
    def test_divide_positive(self):
        self.assertEqual(divide(10, 2), 5.0)

这里用了 assertRaises 检查除以零时是否抛出 ValueError

4. 测试组织和最佳实践

写测试代码多了,你会发现需要点组织技巧,让代码更清晰,维护更省力。

4.1 测试文件和目录结构

  • 测试文件命名:通常以 test_ 开头,比如 test_math_utils.py
  • 目录结构
project/
├── src/
│   └── math_utils.py
├── tests/
│   └── test_math_utils.py
  • 把测试代码和源代码分开,保持项目整洁。

4.2 setUp 和 tearDown

如果每个测试用例都需要初始化一些数据,可以用 setUptearDown 方法:

# test_math_utils.py
import unittest
from math_utils import square
class TestSquare(unittest.TestCase):
    def setUp(self):
        print("Setting up for test")
        self.test_data = [2, -3, 0]
    def test_square_list(self):
        for num in self.test_data:
            result = square(num)
            self.assertEqual(result, num * num)
    def tearDown(self):
        print("Cleaning up after test")
  • setUp:在每个测试用例运行前执行,适合初始化数据。
  • tearDown:在每个测试用例运行后执行,适合清理资源。

4.3 测试用例分组

可以用测试类把相关的测试分组。比如,TestSquareTestDivide 可以写在同一个文件里,但分不同的类。

5. 使用 pytest 框架(更现代的选择)

虽然 unittest 是 Python 内置的,但 pytest 是个更现代、更简洁的测试框架,社区用得更多。安装 pytest:

pip install pytest

pytest 简单例子

还是测试 square 函数:

# test_math_utils_pytest.py
from math_utils import square
def test_positive_number():
    assert square(3) == 9
def test_negative_number():
    assert square(-2) == 4
def test_zero():
    assert square(0) == 0

运行 pytest

pytest test_math_utils_pytest.py

pytest 的优势

  • 更简洁:不用继承 TestCase,直接写函数,断言用普通的 assert
  • 自动发现:运行 pytest 命令会自动找到所有以 test_ 开头的文件和函数。
  • 丰富的插件:支持 mock、coverage 等功能。
  • 详细的错误信息:失败时会清楚告诉你哪里不对。

pytest 常用功能

  • Fixture(类似 setUp):
import pytest
@pytest.fixture
def sample_data():
    return [2, -3, 0]
def test_square_list(sample_data):
    for num in sample_data:
        assert square(num) == num * num
  • 参数化测试
import pytest
@pytest.mark.parametrize("input,expected", [(3, 9), (-2, 4), (0, 0)])
def test_square(input, expected):
    assert square(input) == expected

参数化测试能减少重复代码,特别适合测试多种输入。

6. Mock 和测试外部依赖

现实中,代码经常会依赖外部资源(比如数据库、API)。单元测试要求隔离这些依赖,这时候 unittest.mock 或 pytest 的 pytest-mock 就派上用场了。

Mock 示例

假设你有个函数调用外部 API:

# api_client.py
import requests
def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()

测试时不想真调用 API,可以用 mock:

# test_api_client.py
from unittest.mock import Mock
import pytest
from api_client import get_user_data
def test_get_user_data(mocker):
    # 模拟 requests.get
    mock_get = mocker.patch("requests.get")
    mock_get.return_value = Mock(json=lambda: {"id": 1, "name": "Alice"})
    result = get_user_data(1)
    assert result == {"id": 1, "name": "Alice"}
    mock_get.assert_called_once_with("https://api.example.com/users/1")

这里用 mocker.patch 替换了 requests.get,模拟返回一个假的响应。

7. 测试覆盖率

写完测试后,怎么知道测试覆盖了多少代码?可以用 pytest-cov 插件:

pip install pytest-cov
pytest --cov=src tests/

这会告诉你哪些代码被测试覆盖,哪些没覆盖。目标是尽量提高覆盖率,但 100% 覆盖不一定现实,80%-90% 通常就够用了。

8. 常见问题和解决方案

  • 测试运行太慢:检查是否 mock 了外部依赖,或者用 pytest--durations 参数找出慢的测试。
  • 测试不稳定:可能是没正确隔离依赖,或者测试数据不一致。
  • 测试代码太多:用参数化测试或 fixture 减少重复代码。
  • 不知道测什么:优先测试核心逻辑、边界情况和异常处理。

9. 总结和进阶学习

单元测试是写好 Python 代码的必备技能。unittest 适合快速上手,pytest 更现代化,功能更强。建议:

  • 小项目用 unittest,简单直接。
  • 中大型项目用 pytest,配合插件更高效。
  • 多写边界测试和异常测试,确保代码健壮。
  • 学习 TDD(测试驱动开发),先写测试再写代码,体验会更好。


目录
相关文章
|
3天前
|
测试技术 开发者 Python
Python单元测试入门:3个核心断言方法,帮你快速定位代码bug
本文介绍Python单元测试基础,详解`unittest`框架中的三大核心断言方法:`assertEqual`验证值相等,`assertTrue`和`assertFalse`判断条件真假。通过实例演示其用法,帮助开发者自动化检测代码逻辑,提升测试效率与可靠性。
33 1
|
29天前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
104 18
|
10月前
|
安全 关系型数据库 测试技术
学习Python Web开发的安全测试需要具备哪些知识?
学习Python Web开发的安全测试需要具备哪些知识?
207 61
|
10月前
|
安全 测试技术 网络安全
如何在Python Web开发中进行安全测试?
如何在Python Web开发中进行安全测试?
|
2月前
|
IDE 测试技术 API
python调试与测试
python调试与测试
|
2月前
|
人工智能 Java 测试技术
Java or Python?测试开发工程师如何选择合适的编程语言?
测试工程师如何选择编程语言?Java 还是 Python?多位资深专家分享建议:Python 入门简单、开发效率高,适合新手及自动化测试;Java 生态成熟,适合大型项目和平台开发。建议结合公司技术栈、个人基础及发展方向选择。长远来看,两者兼通更佳,同时关注 Go 等新兴语言。快速学习与实践才是关键。
|
3月前
|
测试技术 Python
Python测试报告生成:整合错误截图,重复用例执行策略,调整测试顺序及多断言机制。
如何组织这一切呢?你可以写一本名为“Python测试之道”的动作指南手册,或者创建一个包含测试策略、测试顺序、多断言机制的脚本库。只要你的测试剧本编写得足够独到,你的框架就会像一位执行任务的超级英雄,将任何潜伏于代码深处的错误无情地揪出来展现在光天化日之下。这些整理好的测试结果,不仅有利于团队协作,更像冒险故事中的精彩篇章,带给读者无尽的探索乐趣和深刻的思考。
106 10
|
3月前
|
测试技术 Python
Python接口自动化测试中Mock服务的实施。
总结一下,Mock服务在接口自动化测试中的应用,可以让我们拥有更高的灵活度。而Python的 `unittest.mock`库为我们提供强大的支持。只要我们正确使用Mock服务,那么在任何情况下,无论是接口是否可用,都可以进行准确有效的测试。这样,就大大提高了自动化测试的稳定性和可靠性。
163 0
|
6月前
|
机器学习/深度学习 设计模式 测试技术
Python 高级编程与实战:构建自动化测试框架
本文深入探讨了Python中的自动化测试框架,包括unittest、pytest和nose2,并通过实战项目帮助读者掌握这些技术。文中详细介绍了各框架的基本用法和示例代码,助力开发者快速验证代码正确性,减少手动测试工作量。学习资源推荐包括Python官方文档及Real Python等网站。
|
6月前
|
存储 JSON API
Python测试淘宝店铺所有商品接口的详细指南
本文详细介绍如何使用Python测试淘宝店铺商品接口,涵盖环境搭建、API接入、签名生成、请求发送、数据解析与存储、异常处理等步骤。通过具体代码示例,帮助开发者轻松获取和分析淘宝店铺商品数据,适用于电商运营、市场分析等场景。遵守法规、注意调用频率限制及数据安全,确保应用的稳定性和合法性。

推荐镜像

更多