unittest
是 Python 自带的单元测试框架,用于编写和运行可重复的测试用例。它的核心思想是通过断言(assertions)验证代码的行为是否符合预期。以下是 unittest
的基本使用方法:
https://bbs.huaweicloud.com/blogs/351714参考这个
1. 基本结构
1.1 创建测试类
- 继承
unittest.TestCase
,每个测试用例对应一个方法。 - 测试方法必须 以
test_
开头,否则不会被自动识别为测试用例。
import unittest
class TestMathOperations(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2) # 断言 1+1=2
1.2 前置与后置方法
setUp()
: 在每个测试方法执行前运行(如初始化资源)。tearDown()
: 在每个测试方法执行后运行(如清理资源)。setUpClass()
/tearDownClass()
: 在整个测试类的开始/结束时运行(需用@classmethod
修饰)。
class TestExample(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("整个测试类开始前执行")
def setUp(self):
print("每个测试方法开始前执行")
def test_example(self):
self.assertTrue(True)
def tearDown(self):
print("每个测试方法结束后执行")
@classmethod
def tearDownClass(cls):
print("整个测试类结束后执行")
2. 断言方法
unittest
提供了丰富的断言方法,常用如下:
方法 | 说明 |
---|---|
assertEqual(a, b) | 检查 a == b |
assertTrue(x) | 检查 x 为 True |
assertFalse(x) | 检查 x 为 False |
assertRaises(Error, func, *args) | 检查函数 func 是否抛出 Error 异常 |
assertIn(a, b) | 检查 a 在 b 中 |
assertIsNone(x) | 检查 x 是 None |
def test_assertions(self):
self.assertEqual(3 * 3, 9)
self.assertIn(2, [1, 2, 3])
with self.assertRaises(ZeroDivisionError):
_ = 1 / 0
3. 运行测试
3.1 通过代码运行
在脚本末尾添加:
if __name__ == "__main__":
unittest.main()
3.2 通过命令行运行
# 运行单个测试模块
python -m unittest test_module.py
# 自动发现并运行所有测试(推荐)
python -m unittest discover
3.3 指定运行特定测试
# 运行单个测试类
python -m unittest test_module.TestClass
# 运行单个测试方法
python -m unittest test_module.TestClass.test_method
4. 测试套件(Test Suite)
手动组织多个测试用例:
suite = unittest.TestSuite()
suite.addTest(TestMathOperations("test_addition"))
suite.addTest(TestExample("test_example"))
runner = unittest.TextTestRunner()
runner.run(suite)
5. 高级用法
5.1 跳过测试
使用装饰器跳过某些测试:
@unittest.skip("跳过原因")
def test_skipped(self):
self.fail("不会执行")
@unittest.skipIf(condition, "条件满足时跳过")
def test_conditional_skip(self):
pass
5.2 参数化测试
unittest
本身不支持参数化,但可通过第三方库(如 parameterized
)实现:
from parameterized import parameterized
class TestParameterized(unittest.TestCase):
@parameterized.expand([
(2, 3, 5),
(0, 0, 0),
])
def test_add(self, a, b, expected):
self.assertEqual(a + b, expected)
5.3 Mock 对象
使用 unittest.mock
模拟外部依赖:
from unittest.mock import Mock
def test_mock(self):
mock_obj = Mock(return_value=42)
self.assertEqual(mock_obj(), 42)
6. 示例项目结构
project/
├── my_code.py # 被测试的代码
└── tests/
├── __init__.py
└── test_code.py # 测试代码
总结
unittest
是 Python 测试的基石,适合中小型项目。对于复杂场景,可以结合第三方库(如 pytest
)增强功能。核心步骤:
- 继承
TestCase
编写测试类。 - 使用
test_
前缀定义测试方法。 - 通过断言验证逻辑。
- 利用
setUp()
/tearDown()
管理资源。 - 运行测试并分析结果。
pytest
是 Python 中最流行的第三方测试框架,以其简洁的语法、强大的功能和灵活的扩展性著称。相比 unittest
,pytest
更注重代码的可读性和可维护性,同时支持丰富的插件生态系统。以下是 pytest
的核心使用方法:
1. 安装 pytest
pip install pytest
2. 基本用法
2.1 编写测试函数
- 测试函数名需以
test_
开头(或_test
结尾)。 - 断言直接使用 Python 原生
assert
语句,无需记忆特定断言方法。
# test_sample.py
def test_addition():
assert 1 + 1 == 2
def test_list_contains():
numbers = [1, 2, 3]
assert 2 in numbers
2.2 运行测试
# 运行当前目录所有测试
pytest
# 运行指定文件
pytest test_sample.py
# 运行指定函数
pytest test_sample.py::test_addition
# 显示详细输出(-v 显示用例名称,-s 打印输出)
pytest -v -s
3. 断言增强
pytest
的断言失败信息更直观,能自动展示上下文差异(如列表、字典比较):
def test_failure_example():
expected = {"a": 1, "b": 2}
actual = {"a": 1, "b": 3}
assert expected == actual
运行后输出:
AssertionError: assert {'a': 1, 'b': 2} == {'a': 1, 'b': 3}
Differing items:
{'b': 2} != {'b': 3}
4. Fixture(依赖注入)
pytest
的 fixture
机制用于管理测试的依赖资源(如数据库连接、临时文件),支持复用和共享。
4.1 定义 Fixture
import pytest
@pytest.fixture
def database_connection():
conn = create_db_connection() # 初始化资源
yield conn # 返回资源
conn.close() # 清理资源
4.2 使用 Fixture
在测试函数中通过参数名直接调用:
def test_query(database_connection):
result = database_connection.query("SELECT * FROM users")
assert len(result) > 0
4.3 Fixture 作用域
通过 scope
参数控制生命周期:
@pytest.fixture(scope="module") # 作用域:模块级(每个模块执行一次)
def shared_resource():
return initialize_resource()
5. 参数化测试
使用 @pytest.mark.parametrize
对单条测试用例注入多组参数,避免重复代码。
import pytest
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 5, 4),
])
def test_add(a, b, expected):
assert a + b == expected
6. 测试异常
使用 pytest.raises
捕获并验证异常:
def test_division_by_zero():
with pytest.raises(ZeroDivisionError):
_ = 1 / 0
7. Mock 对象(依赖隔离)
使用 pytest-mock
插件(基于 unittest.mock
)模拟外部依赖:
pip install pytest-mock
示例:
def test_mocking(mocker):
mock_requests = mocker.patch("requests.get") # 模拟 requests.get
mock_requests.return_value.status_code = 200
response = requests.get("https://api.example.com")
assert response.status_code == 200
8. 插件扩展
pytest
支持丰富的插件,例如:
pytest-cov
: 测试覆盖率统计pytest-xdist
: 并行运行测试pytest-django
: Django 项目集成pytest-asyncio
: 异步测试支持
安装插件:
pip install pytest-cov pytest-xdist
9. 项目结构
project/
├── src/ # 源代码
│ └── my_module.py
└── tests/ # 测试代码
├── __init__.py
├── conftest.py # 全局 Fixture 定义
├── test_core.py
└── test_api.py
10. 与 unittest 兼容
pytest
可以直接运行 unittest
风格的测试用例:
# test_unittest_style.py
import unittest
class TestOldCode(unittest.TestCase):
def test_legacy(self):
self.assertEqual(1 + 1, 2)
运行:
pytest test_unittest_style.py
11. 高级功能
-
标记(Markers):
用@pytest.mark
对测试分类(如跳过、标记为慢测试):@pytest.mark.skip(reason="尚未实现") def test_unimplemented(): assert False @pytest.mark.slow def test_long_running(): # 耗时操作 pass
运行指定标记的测试:
pytest -m slow # 只运行标记为 slow 的测试 pytest -m "not slow" # 排除 slow 测试
-
Hook 函数:
自定义pytest
行为(如修改报告输出)。
总结
pytest
的优势:
- 简洁性:使用原生
assert
,减少样板代码。 - 灵活性:Fixture 机制优雅管理测试依赖。
- 扩展性:通过插件支持复杂场景(如异步、分布式测试)。
- 兼容性:无缝运行
unittest
和nose
测试。
适合从简单脚本到大型项目的全场景测试需求。
Pytest 与 Unittest 全面对比解析
一、核心区别与联系
1. 基本关系
- 联系:
- 两者都是 Python 的测试框架
- 都遵循 xUnit 架构模式(测试用例、测试套件、测试运行器)
- 都支持测试发现、断言、夹具等基本概念
- 区别:
unittest
是 Python 标准库(Python 2.1+)pytest
是第三方库(需要额外安装)
2. 核心差异对比
特性 | unittest | pytest |
---|---|---|
安装要求 | Python 标准库 | pip install pytest |
测试结构 | 必须继承 TestCase | 支持函数和类(无需继承) |
断言系统 | self.assertXxx() 方法 | 原生 assert 语句 |
参数化 | 通过 subTest 或扩展库 | 内置 @pytest.mark.parametrize |
夹具系统 | setUp() /tearDown() 方法 | 灵活的 @pytest.fixture |
测试发现 | 基本发现机制 | 更智能的发现(支持多种命名) |
插件生态 | 有限 | 丰富(800+ 插件) |
报告输出 | 基础文本报告 | 多种格式(HTML, Allure 等) |
执行控制 | 有限命令行选项 | 丰富的命令行参数 |
二、详细功能对比
1. 测试结构
unittest (必须使用类):
import unittest
class TestMath(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
pytest (支持函数和类):
# 函数形式
def test_addition():
assert 1 + 1 == 2
# 类形式(可选)
class TestMath:
def test_addition(self):
assert 1 + 1 == 2
2. 断言系统
unittest (专用方法):
self.assertEqual(a, b)
self.assertTrue(x)
self.assertIn(a, b)
self.assertRaises(ValueError, func)
pytest (原生断言):
assert a == b
assert x
assert a in b
with pytest.raises(ValueError):
func()
3. 参数化测试
unittest (使用 subTest):
class TestParametrized(unittest.TestCase):
def test_addition(self):
test_cases = [
(1, 1, 2),
(2, 3, 5),
(0, 0, 0)
]
for a, b, expected in test_cases:
with self.subTest(a=a, b=b):
self.assertEqual(a + b, expected)
pytest (内置参数化):
import pytest
@pytest.mark.parametrize("a,b,expected", [
(1, 1, 2),
(2, 3, 5),
(0, 0, 0)
])
def test_addition(a, b, expected):
assert a + b == expected
4. 夹具系统 (Fixtures)
unittest (固定方法):
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db = connect_db()
def setUp(self):
self.cursor = self.db.cursor()
def tearDown(self):
self.cursor.close()
@classmethod
def tearDownClass(cls):
cls.db.close()
pytest (灵活夹具):
import pytest
@pytest.fixture(scope="session")
def db():
connection = connect_db()
yield connection
connection.close()
@pytest.fixture
def cursor(db):
cursor = db.cursor()
yield cursor
cursor.close()
def test_query(cursor):
result = cursor.execute("SELECT 1")
assert result == 1
5. 测试发现
unittest 发现规则:
- 文件:
test*.py
- 类:继承
TestCase
- 方法:以
test_
开头
pytest 更灵活的发现:
- 文件:
test_*.py
或*_test.py
- 类:
Test
开头(不强制继承) - 函数:
test_
开头 - 方法:
test_
开头(在任何类中)
6. 命令行控制
unittest 基本命令:
python -m unittest discover # 发现所有测试
python -m unittest test_module # 运行模块
python -m unittest test_module.TestClass # 运行测试类
python -m unittest test_module.TestClass.test_method # 运行测试方法
pytest 丰富选项:
pytest # 运行所有测试
pytest path/to/test.py # 运行指定文件
pytest test.py::TestClass # 运行测试类
pytest test.py::TestClass::test_method # 运行测试方法
pytest -k "add" # 运行名称包含"add"的测试
pytest -m slow # 运行标记为slow的测试
pytest -x # 遇到第一个失败就停止
pytest --maxfail=3 # 最多允许3个失败
pytest -n 4 # 使用4个进程并发运行
7. 插件生态系统
unittest 扩展:
- 有限插件支持
- 常用扩展:
unittest-xml-reporting
(XML报告)parameterized
(参数化)coverage
(覆盖率)
pytest 丰富插件:
- 官方插件仓库:800+ 插件
- 常用插件:
pytest-cov
(代码覆盖率)pytest-xdist
(并发测试)pytest-html
(HTML报告)pytest-mock
(Mock集成)pytest-django
(Django支持)allure-pytest
(Allure报告)
8. 测试报告
unittest 报告:
- 基本文本输出
- 需扩展库生成HTML/XML
- 示例HTML报告工具:
unittest-html-report
pytest 报告:
- 内置丰富的输出格式
- 支持多种报告类型:
- 控制台详细输出 (
-v
) - JUnit XML (
--junitxml
) - HTML (
pytest-html
) - Allure (
allure-pytest
)
- 控制台详细输出 (
三、性能与扩展性对比
指标 | unittest | pytest |
---|---|---|
测试执行速度 | 较快 | 稍慢(但支持并发) |
大型项目支持 | 良好 | 优秀(更好的组织和发现) |
学习曲线 | 平缓(标准库) | 中等(更多概念) |
自定义扩展 | 有限 | 强大(钩子和插件系统) |
社区支持 | 稳定 | 活跃(持续更新) |
四、迁移与共存
1. 从 unittest 迁移到 pytest
# 原 unittest 测试
class TestMath(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
# 迁移到 pytest 后
class TestMath: # 不再需要继承
def test_addition(self):
assert 1 + 1 == 2 # 使用原生断言
2. 共存方案
pytest 可以直接运行 unittest 测试:
# test_unittest_style.py
import unittest
class UnittestTestCase(unittest.TestCase):
def test_unittest_style(self):
self.assertEqual(1 + 1, 2)
# 使用 pytest 运行
# pytest test_unittest_style.py
五、最佳实践建议
选择 unittest 当:
- 项目要求零依赖(只使用标准库)
- 团队熟悉 JUnit 风格测试
- 项目简单,不需要高级特性
- 需要与旧代码库保持兼容
选择 pytest 当:
- 需要更简洁的测试代码
- 需要参数化测试或复杂夹具
- 需要丰富的插件生态系统
- 需要更好的测试报告
- 项目复杂,需要高级测试功能
混合使用策略:
# tests/
# ├── unit/ # unittest 测试
# │ ├── test_utils.py
# │ └── test_core.py
# ├── integration/ # pytest 测试
# │ ├── test_api.py
# │ └── test_db.py
# └── run_tests.py
# run_tests.py
import unittest
import pytest
if __name__ == "__main__":
# 运行 unittest 测试
unittest_suite = unittest.defaultTestLoader.discover('tests/unit')
unittest.TextTestRunner().run(unittest_suite)
# 运行 pytest 测试
pytest.main(['tests/integration'])
六、典型应用场景
unittest 适用场景:
- 标准库开发
- 嵌入式系统(依赖最小化)
- 教学场景(展示基础概念)
- 维护遗留测试代码
pytest 适用场景:
- Web 框架测试(Django, Flask)
- 数据科学项目(参数化测试)
- API 测试(复杂夹具管理)
- 大型企业应用(插件集成)
- CI/CD 流水线(丰富报告)
七、总结对比表
维度 | unittest | pytest | 优势方 |
---|---|---|---|
易用性 | 中等 | 高 | pytest |
灵活性 | 低 | 高 | pytest |
扩展性 | 低 | 高 | pytest |
零依赖 | 是 | 否 | unittest |
学习曲线 | 平缓 | 中等 | unittest |
社区支持 | 稳定 | 活跃 | pytest |
报告功能 | 基础 | 丰富 | pytest |
并发支持 | 手动 | 内置 | pytest |
参数化 | 有限 | 强大 | pytest |
代码简洁 | 冗长 | 简洁 | pytest |
八、迁移指南
从 unittest 迁移到 pytest 的步骤:
-
安装 pytest:
pip install pytest
-
移除 TestCase 继承:
- class TestMath(unittest.TestCase): + class TestMath:
-
替换断言方法:
- self.assertEqual(a, b) + assert a == b - self.assertTrue(x) + assert x - with self.assertRaises(ValueError): + with pytest.raises(ValueError):
-
转换参数化测试:
# 原 subTest for a, b, expected in cases: with self.subTest(a=a, b=b): self.assertEqual(a + b, expected) # 转换为 @pytest.mark.parametrize("a,b,expected", cases) def test_add(a, b, expected): assert a + b == expected
-
重构夹具:
# 原 setUp/tearDown def setUp(self): self.resource = create_resource() def tearDown(self): self.resource.cleanup() # 转换为 @pytest.fixture def resource(): r = create_resource() yield r r.cleanup()
-
运行混合测试:
pytest # 自动运行所有测试(包括 unittest 风格)
九、结论
unittest
和 pytest
都是强大的 Python 测试框架,各有优势:
-
unittest 是 Python 标准库的一部分,适合:
- 需要零依赖的项目
- 简单测试场景
- 维护旧有测试代码
-
pytest 是更现代的测试框架,适合:
- 新项目和复杂场景
- 需要高级功能(参数化、丰富夹具)
- 需要高质量测试报告
- 大型项目和团队协作
推荐策略:
- 新项目优先选择
pytest
- 已有
unittest
项目逐步迁移到pytest
- 混合项目中使用
pytest
运行所有测试(包括 unittest 测试)
两者可以和谐共存于同一项目中,pytest
的兼容性设计使得迁移过程可以循序渐进,让团队能够根据实际需求灵活选择最适合的测试策略。