7.2 KiB
sources, brief
| sources | brief | |||
|---|---|---|---|---|
|
单元测试是验证函数、类或模块等最小代码单元行为是否符合预期的测试方法。 |
单元测试
单元测试是一种软件测试方法,用于验证程序中较小、相对独立的代码单元是否按照预期工作。这些单元可以是函数、方法、类或模块。在 Python 中,单元测试尤其重要,因为 Python 是动态语言,很多错误不会在编译阶段暴露,只能通过运行代码和测试行为来发现。相关背景见 summaries/01_Testing。
为什么需要单元测试
summaries/01_Testing 强调:Python 没有编译器替开发者提前发现大量错误,因此发现 bug 的主要方式是运行代码。单元测试通过系统化地运行一组小规模测试,帮助开发者确认代码行为是否正确。
单元测试的主要价值包括:
- 尽早发现函数、类或模块中的错误。
- 验证代码在典型输入下是否返回正确结果。
- 验证代码在错误输入下是否抛出预期异常。
- 防止后续修改破坏已有功能。
- 为代码行为提供可执行的文档。
这与 软件测试 的整体目标一致,但单元测试更关注最小粒度代码单元。
Python 中的单元测试
Python 标准库提供了 unittest 模块,用于编写结构化单元测试。典型做法是将业务代码和测试代码分开,例如:
# simple.py
def add(x, y):
return x + y
对应测试文件可以写成:
# test_simple.py
import simple
import unittest
class TestAdd(unittest.TestCase):
def test_simple(self):
r = simple.add(2, 2)
self.assertEqual(r, 4)
def test_str(self):
r = simple.add('hello', 'world')
self.assertEqual(r, 'helloworld')
这里有几个关键规则:
- 测试类通常继承自
unittest.TestCase。 - 测试方法名称必须以
test开头,测试运行器才会识别。 - 每个测试方法验证一个具体行为或场景。
- 使用断言方法表达期望结果。
相关概念:Python unittest、测试用例、测试运行器。
测试断言
单元测试的核心是断言:声明某个条件应该成立。如果断言失败,测试就失败。
在 unittest 中,常见断言包括:
self.assertTrue(expr) # 判断表达式为 True
self.assertEqual(x, y) # 判断 x == y
self.assertNotEqual(x, y) # 判断 x != y
self.assertAlmostEqual(x, y, places) # 判断数值近似相等
self.assertRaises(exc, callable, ...) # 判断调用会抛出指定异常
例如,测试 add(2, 2) 是否等于 4:
self.assertEqual(simple.add(2, 2), 4)
如果实际结果不是 4,测试框架会报告失败原因。
相关概念:concepts/断言、测试断言。
运行单元测试
使用 unittest 时,测试文件通常包含:
if __name__ == '__main__':
unittest.main()
然后可以直接运行:
python3 test_simple.py
运行后,unittest 会报告:
- 执行了多少个测试。
- 哪些测试通过。
- 哪些测试失败。
- 失败位置和断言错误信息。
例如,如果测试期望 5,但实际结果是 4,会出现类似:
AssertionError: 4 != 5
这类失败报告能帮助开发者快速定位错误行为。
测试异常情况
单元测试不仅要测试正常输入,也应该测试错误输入。对于应当抛出异常的场景,可以使用 assertRaises。
在 summaries/01_Testing 的练习中,Stock 类的 shares 属性应该只能设置为整数。如果将其设置为字符串,应抛出 TypeError:
def test_bad_shares(self):
s = stock.Stock('GOOG', 100, 490.1)
with self.assertRaises(TypeError):
s.shares = '100'
这种测试能确认代码不仅在正确使用时有效,也能在错误使用时按预期失败。
相关概念:异常测试、类型检查。
面向对象代码的单元测试
单元测试也常用于测试类的行为。summaries/01_Testing 中的练习要求为 Stock 类编写测试,覆盖以下内容:
- 对象是否能正确创建。
- 属性值是否正确保存。
- 计算属性是否返回正确结果。
- 方法调用是否正确改变对象状态。
- 属性类型约束是否生效。
示例:
class TestStock(unittest.TestCase):
def test_create(self):
s = stock.Stock('GOOG', 100, 490.1)
self.assertEqual(s.name, 'GOOG')
self.assertEqual(s.shares, 100)
self.assertEqual(s.price, 490.1)
进一步可以测试:
s.cost是否返回49010.0。s.sell()是否能正确减少s.shares。s.shares = '100'是否抛出TypeError。
相关概念:面向对象测试、属性测试。
与内联测试的区别
简单断言也可以直接写在模块中:
def add(x, y):
return x + y
assert add(2, 2) == 4
这种方式可以作为基本的“冒烟测试”:如果模块导入时就失败,说明代码存在明显问题。
但内联测试不适合大型或系统化测试,因为:
- 测试代码和业务代码混在一起。
- 难以组织大量测试场景。
- 难以生成完整测试报告。
- 不适合复杂项目中的自动化测试流程。
因此,正式项目通常使用 unittest 或 pytest 等测试框架来组织单元测试。
相关概念:冒烟测试、测试组织。
unittest 与 pytest
unittest 是 Python 标准库的一部分,优点是无需额外安装,适合任何 Python 环境。但它的写法相对冗长。
pytest 是常用第三方测试工具,语法更简洁。例如:
import simple
def test_simple():
assert simple.add(2, 2) == 4
def test_str():
assert simple.add('hello', 'world') == 'helloworld'
运行方式通常是:
python -m pytest
pytest 会自动发现测试并执行。它也支持直接使用 Python 的 assert 语句,使测试代码更接近普通函数。
相关概念:concepts/pytest、测试发现、Python测试工具。
良好单元测试的特征
一个好的单元测试通常具备以下特点:
- 范围小:只验证一个函数、方法或行为。
- 目标明确:清楚表达期望结果。
- 可重复运行:每次运行结果应一致。
- 失败信息有意义:失败时能帮助定位问题。
- 覆盖正常和异常场景:不仅测试成功路径,也测试错误路径。
- 与业务代码分离:通常放在独立测试文件中。
小结
单元测试是 Python 开发中的基础实践。它通过运行小规模、可重复的测试来验证代码行为,弥补动态语言缺少编译期检查的不足。summaries/01_Testing 展示了从简单断言到 unittest,再到 pytest 的测试方式,并通过 Stock 类练习说明如何测试对象创建、属性计算、方法行为和异常情况。
相关页面:summaries/01_Testing、软件测试、concepts/断言、Python unittest、concepts/pytest、异常测试。
See also: summaries/Contents