AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

确定性断言与代码测试

我的学习 →

正在读取学习记录…

确定性断言是一条可以由程序重复判断的条件:同样的输入和状态,每次都应得到同样的真假结果。退款助手回复“已成功退款”以后,系统可以检查订单号是否正确、金额是否为100元、账本是否真的新增一笔退款。

这像交货验收:包装完整只是第一关,还要核对收货人、数量和实际到货。把所有检查压成一句“看着挺好”,一旦失败,既找不到原因,也容易让漂亮的包装蒙混过关。

正常退款需要四份明确条件

订单 order-1 属于 user-a,已支付100元。任务要求全额退款一次。正常输出带五个字段:status 是refunded,order_id 是目标订单,owner 是当前用户,amount 是整数100,receipt 是退款回执编号refund-1。账本中还应有且只有一笔目标订单的退款,内容与回执对应。

先把要求拆成四项。structure 检查结构:字段、类型与状态值。ownership 检查订单和用户是否匹配原单。amount 检查是否退了应退的100元。effect 检查账本中的事务效果是否存在、唯一,并与输出一致。总体通过要求四项同时满足。

“效果存在”与“业务正确”可以分别失败:退给另一用户的记录可能真实存在,所以effect通过,但ownership失败。若退款金额是90元且账本也记90元,effect通过,amount仍然失败。细分检查能保留这类差异。

代码断言适合检查什么

字段类型、可枚举状态、数值边界、已有数据库记录都适合写成代码。回答是否体贴、解释是否清晰等开放质量,往往还需要有明确Rubric的评审。确定性是评分器的重复性;被测模型可以每次产生不同输出,评分器仍对每份输出按同一规则检查。

下面程序用五组预置输出与账本快照运行评分器。它依次展示正常、错用户、错金额、缺效果和重复效果。账本在例子中是一份列表,便于读懂“找目标订单的一笔事务”这件事;实际系统可以把读取接口接到测试数据库或可核查的退款回执。

结构、归属、金额和账本效果四项检查汇合,只有全部为真总体才通过
图1:四项检查分别定位问题,总体结果采用全部满足的规则。

从空目录保存并运行

在 PowerShell 中进入你平时使用的学习目录,再创建本节文件夹并进入:

Bash
mkdir l7-assertions
cd l7-assertions

用编辑器新建文件,把下面完整代码保存为当前文件夹的 assertions_lab.py。保存类型选“所有文件”,编码选 UTF-8;资源管理器开启“文件扩展名”,核对文件名以 .py 结尾。数据已经写在代码里,可以直接运行。

Python
"""对退款结果分别检查结构、归属、金额和实际账本效果。"""

from copy import deepcopy


ORDER = {"order_id": "order-1", "owner": "user-a", "paid": 100}
GOOD = {"status": "refunded", "order_id": "order-1", "owner": "user-a",
        "amount": 100, "receipt": "refund-1"}
LEDGER = [{"receipt": "refund-1", "order_id": "order-1", "owner": "user-a", "amount": 100}]


def grade(output, ledger):
    """每个布尔结果对应一条明确契约,最终通过要求全部满足。"""
    fields = {"status", "order_id", "owner", "amount", "receipt"}
    structure = (isinstance(output, dict) and set(output) == fields
                 and output.get("status") == "refunded"
                 and all(type(output.get(key)) is str and output[key].strip()
                         for key in ("order_id", "owner", "receipt"))
                 and type(output.get("amount")) is int)
    # 先守住结构;后续每项独立给出结果,避免KeyError遮住真正问题。
    belongs = structure and output["order_id"] == ORDER["order_id"] and (
        output["owner"] == ORDER["owner"])
    amount = structure and output["amount"] == ORDER["paid"]
    # 查整笔事务的行数和内容,重复退款不能因找到一条匹配记录而通过。
    matching = [row for row in ledger if row.get("order_id") == ORDER["order_id"]]
    effect = (structure and len(matching) == 1
              and matching[0] == {key: output[key]
                                 for key in ("receipt", "order_id", "owner", "amount")})
    checks = {"structure": bool(structure), "ownership": bool(belongs),
              "amount": bool(amount), "effect": bool(effect)}
    return checks, all(checks.values())


def scenarios():
    """固定五种结果;每种错误只改变一个业务因素。"""
    return [
        ("good", deepcopy(GOOD), deepcopy(LEDGER)),
        ("wrong-owner", dict(GOOD, owner="user-b"),
         [dict(LEDGER[0], owner="user-b")]),
        ("wrong-amount", dict(GOOD, amount=90), [dict(LEDGER[0], amount=90)]),
        ("missing-effect", deepcopy(GOOD), []),
        ("duplicate-effect", deepcopy(GOOD), deepcopy(LEDGER + LEDGER)),
    ]


def main():
    for name, output, ledger in scenarios():
        checks, passed = grade(output, ledger)
        detail = " ".join(f"{key}={'通过' if value else '失败'}"
                          for key, value in checks.items())
        print(f"{name}: {detail} 总体={'通过' if passed else '失败'}")


if __name__ == "__main__":
    main()

回到刚才的 PowerShell,确认文件已经保存,再运行:

Bash
ls
python -X utf8 assertions_lab.py

程序输出:

TXT
good: structure=通过 ownership=通过 amount=通过 effect=通过 总体=通过
wrong-owner: structure=通过 ownership=失败 amount=通过 effect=通过 总体=失败
wrong-amount: structure=通过 ownership=通过 amount=失败 effect=通过 总体=失败
missing-effect: structure=通过 ownership=通过 amount=通过 effect=失败 总体=失败
duplicate-effect: structure=通过 ownership=通过 amount=通过 effect=失败 总体=失败

五行输出各自暴露哪条契约

good 的结构、归属、金额和效果都通过,因此总体通过。wrong-owner 的格式合法,确实存在一条与输出相同的账本记录,但用户为user-b,所以只有ownership失败。它说明“写进账本了”还不能说明“写对了”。

wrong-amount 已退90元,因此amount失败;记录与输出一致,effect仍通过。missing-effect 的输出看上去正确,但账本为空,effect失败。duplicate-effect 账本有两条目标单退款,仍然effect失败,防止“找到其中一条匹配记录就算成功”。

代码先判断structure,才访问可能缺失的字段。金额用 type(value) is int 检查,因为True不能作为1元。matching 先筛出目标订单的记录,其他订单不会造成重复;目标记录必须恰好一条,且回执、订单、用户、金额都相同。

总体逻辑写成:

TXT
accepted = structure AND ownership AND amount AND effect

若统计这五个固定场景的总体通过率,通过数为1、分母为5,所以1 ÷ 5 = 20%。这组数据由一条正常与四条错误案例组成,分开报告检查项比单看这个总体比例更能定位问题。

补一条账本记录,看看哪个失败消失

在 scenarios() 中,把missing-effect这一行最后的空列表 [] 改成 deepcopy(LEDGER),保存并运行 python -X utf8 assertions_lab.py。这行的effect和总体会从失败变为通过,其他四行保持原结果。恢复空列表,再运行测试。

再留意wrong-owner:仅给它补更多账本记录不能修复用户归属;问题必须在产生退款之前解决。评测器用细项指出问题,被测业务执行器负责避免错误动作。评分器本身也需要测试,尤其是合法格式、错误业务、重复效果这些容易混淆的组合。

把断言写得细,但不要拘泥无关表面

任务要求整数100元,检查整数100有明确业务理由。若要求自由格式解释,就不要把一整段文案逐字相等设成通过条件;正确措辞的变化会被误伤。对于可以采用多条合法路线的任务,优先检查必要条件与结果,而不是无关的调用顺序。

Python unittest 文档可查看assertEqual、assertTrue和assertRaises的使用方式。Anthropic 的代码评分器说明比较了确定性检查与开放质量评审的适用范围,重点读code-based graders与评分器的脆弱性。

保存测试并检查关键条件

7项测试检查正确退款、格式合法但用户错误、金额错误、空账本、重复效果、字段与类型错误,以及无关订单不干扰目标订单检查。 将下方代码保存为同一文件夹的 test_assertions_lab.py,运行:

Bash
ls
python -X utf8 -m unittest -v test_assertions_lab.py

看到 Ran 7 tests 与 OK 表示这些固定条件全部通过。若找不到文件,先用 dir 核对当前文件夹及两份文件名;若出现具体断言失败,先恢复正文中的代码与预置数据,再对照该项测试的条件。

完整测试:test_assertions_lab.py
Python
"""确保格式合法不能掩盖业务错误,业务效果必须真正存在。"""

from copy import deepcopy
from pathlib import Path
import sys
import unittest

sys.path.insert(0, str(Path(__file__).parent))
import assertions_lab as lab


class AssertionTests(unittest.TestCase):
    def test_good_result_passes_all_checks(self):
        checks, passed = lab.grade(lab.GOOD, lab.LEDGER)
        self.assertTrue(passed)
        self.assertTrue(all(checks.values()))

    def test_valid_structure_can_have_wrong_owner(self):
        output = dict(lab.GOOD, owner="user-b")
        checks, passed = lab.grade(output, [dict(lab.LEDGER[0], owner="user-b")])
        self.assertTrue(checks["structure"])
        self.assertFalse(checks["ownership"])
        self.assertFalse(passed)

    def test_wrong_amount_is_separate_failure(self):
        output = dict(lab.GOOD, amount=90)
        checks, passed = lab.grade(output, [dict(lab.LEDGER[0], amount=90)])
        self.assertTrue(checks["ownership"])
        self.assertFalse(checks["amount"])
        self.assertFalse(passed)

    def test_empty_ledger_cannot_prove_success(self):
        checks, passed = lab.grade(lab.GOOD, [])
        self.assertFalse(checks["effect"])
        self.assertFalse(passed)

    def test_duplicate_effect_fails(self):
        checks, passed = lab.grade(lab.GOOD, lab.LEDGER + lab.LEDGER)
        self.assertFalse(checks["effect"])
        self.assertFalse(passed)

    def test_missing_field_and_boolean_amount_fail_structure(self):
        missing = deepcopy(lab.GOOD)
        del missing["receipt"]
        for output in (missing, dict(lab.GOOD, amount=True), "已退款"):
            checks, passed = lab.grade(output, lab.LEDGER)
            self.assertFalse(checks["structure"])
            self.assertFalse(passed)

    def test_unrelated_order_does_not_create_duplicate(self):
        ledger = deepcopy(lab.LEDGER) + [dict(lab.LEDGER[0], order_id="order-2")]
        self.assertTrue(lab.grade(lab.GOOD, ledger)[1])


if __name__ == "__main__":
    unittest.main()
课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.1.0

选择题 · 结构合法但退款金额错误,哪些检查能发现

结构合法但退款金额错误,哪些检查能发现?

对应知识点:L7-assertions

选择一个答案
练习 2 / 4 · v1.1.0

简答题 · 解释机制

一条断言失败应提供哪些信息便于定位?

对应知识点:L7-assertions

练习 3 / 4 · v1.1.0

代码题 · 核对给定数据

订单金额100,结果金额1000,写出会失败的金额断言。

对应知识点:L7-assertions

练习 4 / 4 · v1.1.0

场景题 · 分析具体变化

一次结果有字段、工具和金额三项错误,评测应怎样记录?

对应知识点:L7-assertions

图片查看器
100%

文章配图 · 按 Esc 关闭