Files
coding-mentor-agent/kb/python-course-kb-practical-python/wiki/concepts/断言.md
T

6.3 KiB
Raw Blame History

sources, brief
sources brief
summaries/08_Testing_debugging__00_Overview.md
summaries/03_Debugging.md
summaries/01_Testing.md
断言是在运行时检查程序内部假设是否成立的机制。

断言

断言是一种运行时检查机制,用来验证程序内部的假设、约束或不变量是否成立。在 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 中,断言主要用于以下场景:

  1. 检查程序内部状态是否符合预期。
  2. 验证函数参数是否满足内部约定。
  3. 表达代码中的不变量。
  4. 编写简单的内联测试或冒烟测试。

断言的核心作用不是处理外部错误,而是帮助程序员尽早发现“理论上不应该发生”的情况。

内部检查与不变量

断言适合用于检查程序内部不变量,也就是在程序正确运行时始终应该成立的条件。

例如:

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

See also: summaries/08_Testing_debugging__00_Overview