6.3 KiB
sources, brief
| sources | brief | |||
|---|---|---|---|---|
|
断言是在运行时检查程序内部假设是否成立的机制。 |
断言
断言是一种运行时检查机制,用来验证程序内部的假设、约束或不变量是否成立。在 Python 中,断言通过 assert 语句实现;如果被检查的表达式为假,就会抛出 AssertionError。
相关来源:summaries/01_Testing
基本形式
Python 的断言语法如下:
assert <expression> [, 'Diagnostic message']
如果 <expression> 的结果为 True,程序继续执行;如果结果为 False,Python 会抛出 AssertionError。可选的诊断消息用于说明失败原因。
示例:
assert isinstance(10, int), 'Expected int'
这个断言检查 10 是否是整数。如果不是,就会抛出带有 Expected int 信息的异常。
断言的用途
在 summaries/01_Testing 中,断言主要用于以下场景:
- 检查程序内部状态是否符合预期。
- 验证函数参数是否满足内部约定。
- 表达代码中的不变量。
- 编写简单的内联测试或冒烟测试。
断言的核心作用不是处理外部错误,而是帮助程序员尽早发现“理论上不应该发生”的情况。
内部检查与不变量
断言适合用于检查程序内部不变量,也就是在程序正确运行时始终应该成立的条件。
例如:
def add(x, y):
assert isinstance(x, int), 'Expected int'
assert isinstance(y, int), 'Expected int'
return x + y
这里的两个断言表达了函数 add() 的内部约定:调用者应该传入整数。如果调用者传入字符串,就会立即失败:
add('2', '3')
# AssertionError: Expected int
这种做法可以让错误在靠近源头的位置暴露,避免错误数据继续流入后续逻辑。
相关概念:程序不变量、契约式编程、类型检查
与契约式编程的关系
断言常用于 契约式编程。契约式编程要求软件组件明确自己的接口约定,例如:
- 调用函数前必须满足什么条件,即前置条件。
- 函数执行后应该保证什么结果,即后置条件。
- 对象在生命周期中必须始终满足什么约束,即不变量。
在 Python 中,可以用 assert 显式写出这些约束:
def withdraw(balance, amount):
assert amount > 0, 'amount must be positive'
assert balance >= amount, 'insufficient balance'
return balance - amount
这些断言不是为了美化代码,而是为了让接口假设变得清晰、可执行、可检查。
断言与测试
断言也可以用于简单测试。例如:
def add(x, y):
return x + y
assert add(2, 2) == 4
这类测试通常被称为内联测试或冒烟测试。它们可以快速验证代码是否明显损坏。如果断言失败,模块在导入或运行时就会报错。
不过,summaries/01_Testing 强调,内联断言不适合替代完整的测试体系。对于更系统的测试,应使用 Python unittest、concepts/pytest 等测试工具。
相关概念:软件测试、concepts/单元测试、冒烟测试、测试断言
不应用断言校验用户输入
断言不应该用于检查用户输入,例如 Web 表单、命令行参数、文件内容或网络请求数据。
原因是:
- 用户输入属于外部数据,错误是正常情况,应使用显式错误处理。
- 断言主要表达程序员假设,而不是业务校验规则。
- Python 可以在优化模式下禁用断言,使用
python -O运行时,assert语句可能不会执行。
因此,下面这种写法不适合用于生产级用户输入校验:
assert user_age >= 0, 'age must be non-negative'
更合适的写法是显式检查并抛出合适异常或返回错误信息:
if user_age < 0:
raise ValueError('age must be non-negative')
相关概念:concepts/异常处理、输入校验
断言与 unittest 断言的区别
Python 的 assert 语句和 unittest.TestCase 中的断言方法都用于检查条件,但用途不同。
普通 assert:
assert x == y
常用于内部检查、契约式编程或简单测试。
unittest 断言:
self.assertEqual(x, y)
self.assertTrue(expr)
self.assertRaises(TypeError, func)
常用于结构化单元测试。它们能提供更清晰的测试报告,并与测试运行器、测试发现、结果统计等机制集成。
在 summaries/01_Testing 中,unittest 被用于创建独立测试文件和测试类,例如为 Stock 类测试属性、方法和异常行为。
相关概念:Python unittest、异常测试、测试运行器
使用建议
使用断言时可以遵循以下原则:
- 用断言检查“如果程序正确,这里一定为真”的条件。
- 用断言暴露程序员错误,而不是处理用户错误。
- 为断言添加清晰的诊断消息,便于定位问题。
- 不要把断言作为完整测试体系的替代品。
- 对外部输入和业务错误使用显式异常处理。
- 在重要库或应用中,结合 concepts/单元测试 和 concepts/pytest 建立更完整的测试覆盖。
简要示例
适合使用断言的场景:
def average(values):
assert len(values) > 0, 'values must not be empty'
return sum(values) / len(values)
适合使用显式异常的场景:
def parse_age(text):
age = int(text)
if age < 0:
raise ValueError('age must be non-negative')
return age
前者偏向内部假设检查;后者偏向外部输入校验。
相关概念
- summaries/01_Testing:介绍 Python 测试、断言、
unittest和pytest的基础用法。 - 软件测试:通过运行代码验证程序行为是否符合预期。
- 契约式编程:用前置条件、后置条件和不变量定义组件接口。
- 程序不变量:程序正确执行时必须始终成立的条件。
- concepts/单元测试:对函数、类或模块进行小粒度验证。
- Python unittest:Python 标准库中的单元测试框架。
- concepts/pytest:常用第三方 Python 测试框架。
- 异常测试:验证代码在错误条件下是否抛出预期异常。
See also: summaries/03_Debugging