Files
coding-mentor-agent/kb/python-course-kb-practical-python/wiki/summaries/01_Testing.md
T

7.7 KiB
Raw Blame History

doc_type, full_text
doc_type full_text
short sources/01_Testing.md

01_Testing 总结

本文介绍 Python 中测试的基本思想与实践方式,强调动态语言缺少编译期检查,因此需要通过运行代码和系统化测试来发现问题。核心内容包括 assert 断言、契约式编程、内联冒烟测试、标准库 unittest、第三方工具 pytest,以及围绕 Stock 类编写单元测试的练习。

测试的重要性

Python 的动态特性使测试对大多数应用至关重要:

  • 没有编译器帮助提前发现大量类型或接口错误。
  • 发现 bug 的主要方式是运行代码。
  • 测试需要尽可能覆盖程序功能,验证代码行为符合预期。

文章用一句话概括态度:测试很棒,调试很糟。也就是说,主动编写测试比事后依赖调试更可靠。

相关概念:concepts/软件测试、Python动态类型

assert 断言

assert 是程序内部检查机制。如果表达式不为真,就会抛出 AssertionError。

assert <expression> [, 'Diagnostic message']

示例:

assert isinstance(10, int), 'Expected int'

assert 适合用于检查程序内部不变量和假设,不适合用于校验用户输入。例如,不应依赖 assert 检查 Web 表单提交的数据。

相关概念:concepts/断言、程序不变量

契约式编程

契约式编程,也称 Design by Contract,是大量使用断言来定义组件接口规格的一种设计方法。

例如,可以在函数入口检查参数类型:

def add(x, y):
    assert isinstance(x, int), 'Expected int'
    assert isinstance(y, int), 'Expected int'
    return x + y

这样可以尽早发现调用者传入了不符合预期的参数:

>>> add('2', '3')
AssertionError: Expected int

这种方式将函数对调用者的要求明确写进代码中,有助于定位接口使用错误。

相关概念:契约式编程、接口设计、类型检查

内联测试

断言也可以用作简单测试:

def add(x, y):
    return x + y

assert add(2, 2) == 4

这种测试直接放在模块代码中。它的优点是:如果代码明显损坏,导入模块时就会失败。

不过,内联断言不适合做全面测试,更适合作为基础的“冒烟测试”:确认函数在最简单的例子上是否能正常工作。

相关概念:冒烟测试、测试组织

unittest 模块

Python 标准库提供 unittest 模块,用于编写结构化单元测试。

假设有业务代码:

# simple.py

def add(x, y):
    return x + y

可以创建单独的测试文件:

# test_simple.py

import simple
import unittest

测试类必须继承自 unittest.TestCase:

class TestAdd(unittest.TestCase):
    ...

测试方法必须以 test 开头,否则不会被测试运行器自动识别:

class TestAdd(unittest.TestCase):
    def test_simple(self):
        r = simple.add(2, 2)
        self.assertEqual(r, 5)

    def test_str(self):
        r = simple.add('hello', 'world')
        self.assertEqual(r, 'helloworld')

相关概念:concepts/单元测试、Python unittest、测试用例

unittest 常用断言

unittest.TestCase 提供多种断言方法,用于表达不同测试期望:

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, ...)  # 判断调用会抛出指定异常

这些只是部分方法,unittest 还提供了更多断言、测试运行器和结果收集功能。

相关概念:测试断言、异常测试

运行 unittest

测试文件通常包含如下入口:

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

然后可以直接运行测试文件:

python3 test_simple.py

如果测试失败,unittest 会报告失败的测试方法、调用栈和断言失败原因。例如 self.assertEqual(r, 5) 在实际结果为 4 时会显示:

AssertionError: 4 != 5

测试输出会统计运行数量、耗时以及失败情况。

相关概念:测试运行器、测试失败报告

第三方测试工具:pytest

虽然 unittest 是标准库的一部分,优点是随 Python 可用,但许多程序员认为它较为冗长。文章介绍了常见替代方案 pytest。

使用 pytest 时,测试文件可以更简洁:

# test_simple.py
import simple

def test_simple():
    assert simple.add(2, 2) == 4

def test_str():
    assert simple.add('hello', 'world') == 'helloworld'

运行方式:

python -m pytest

pytest 会自动发现测试并执行。文章指出,pytest 功能远不止这个例子,但入门通常很容易。

相关概念:concepts/pytest、测试发现、Python测试工具

练习:为 Stock 类编写单元测试

练习要求为之前实现的 Stock 类编写测试文件 test_stock.py。该类来自前面关于 typed-properties 的练习。

起始测试用于验证实例创建:

import unittest
import 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)

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

运行成功时输出类似:

.
----------------------------------------------------------------------
Ran 1 tests in 0.000s

OK

随后需要补充测试:

  1. s.cost 属性是否返回正确值 49010.0。
  2. s.sell() 方法是否能正确减少 s.shares。
  3. s.shares 是否不能被设置为非整数值。

测试异常可以使用上下文管理器形式的 assertRaises:

def test_bad_shares(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.shares = '100'

这个练习将单元测试应用到对象创建、属性计算、方法副作用和类型约束验证等场景。

相关概念:面向对象测试、属性测试、异常测试

核心要点

  • Python 动态语言特性使测试尤其重要。
  • assert 用于程序内部检查和不变量验证,不应用于用户输入验证。
  • 契约式编程通过断言明确接口前置条件。
  • 内联断言适合做简单冒烟测试,但不适合完整测试体系。
  • unittest 是 Python 标准单元测试框架,基于 TestCase、test_ 方法和断言方法。
  • unittest.main() 可用于从脚本运行测试。
  • pytest 提供更简洁的测试编写和自动发现机制。
  • 编写测试应覆盖对象初始化、属性、方法行为和异常情况。

可延伸的概念页

  • 软件测试:测试在软件开发中的作用与层次。
  • concepts/单元测试:围绕函数、类和模块的最小粒度验证。
  • concepts/断言:运行时假设检查与测试表达方式。
  • 契约式编程:通过前置条件、后置条件和不变量设计接口。
  • Python unittest:Python 标准测试框架的结构与用法。
  • concepts/pytest:Python 第三方测试框架及其测试发现机制。
  • 异常测试:验证代码在错误输入下是否抛出预期异常。