上一课验证了"重复请求不会重复保存"。但以后修改代码,怎样知道这个功能没有被改坏?
这节把人工观察变成自动检查:
- 相同请求只保存一条记录。
- 同一请求编号不能更换内容。
- 不同请求可以分别保存。
- 不同用户的请求不会相互混淆。
不调用模型,不消耗 API 额度,也不修改之前的模拟订单数据库。
1. 新建测试文件
在上一课文件所在目录,新建:
C:\Users\Administrator\langchain-study\test_unit5_lesson10_idempotency.py
写入:
import sqlite3
import unittest
from contextlib import closing
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import patch
import unit5_lesson9_idempotency as orders
class TestIdempotency(unittest.TestCase):
def setUp(self):
# 每个测试使用独立的临时目录
self.temp_dir = TemporaryDirectory()
self.addCleanup(self.temp_dir.cleanup)
self.db_path = Path(self.temp_dir.name) / "test_orders.sqlite"
# 临时替换上一课模块里的数据库路径
patcher = patch.object(orders, "DB_PATH", self.db_path)
patcher.start()
self.addCleanup(patcher.stop)
def save(
self,
request_id="request-1",
quantity=2,
total_yuan="81.00",
user_id="A",
):
"""测试辅助函数,减少重复填写参数。"""
return orders.save_simulated_order(
user_id=user_id,
request_id=request_id,
book_name="三体",
quantity=quantity,
total_yuan=total_yuan,
)
def test_same_request_is_saved_once(self):
first_id, first_created = self.save()
second_id, second_created = self.save()
self.assertTrue(first_created)
self.assertFalse(second_created)
self.assertEqual(first_id, second_id)
# 不仅检查返回值,也检查数据库实际记录数
with closing(sqlite3.connect(self.db_path)) as conn:
count = conn.execute(
"SELECT COUNT(*) FROM simulated_orders"
).fetchone()[0]
self.assertEqual(count, 1)
def test_changed_payload_is_rejected(self):
original_id, _ = self.save()
# 请求编号不变,却把2本改成3本,必须拒绝
with self.assertRaises(ValueError):
self.save(quantity=3, total_yuan="121.50")
# 冲突发生后,原记录仍应保持不变
saved_id, created = self.save()
self.assertEqual(saved_id, original_id)
self.assertFalse(created)
def test_different_requests_create_different_records(self):
first_id, first_created = self.save(request_id="request-1")
second_id, second_created = self.save(request_id="request-2")
self.assertTrue(first_created)
self.assertTrue(second_created)
self.assertNotEqual(first_id, second_id)
def test_same_request_id_for_different_users(self):
first_id, first_created = self.save(user_id="A")
second_id, second_created = self.save(user_id="B")
self.assertTrue(first_created)
self.assertTrue(second_created)
self.assertNotEqual(first_id, second_id)
if __name__ == "__main__":
unittest.main(verbosity=2)
最后一个测试只验证记录按用户区分,不验证会员价格;这里的金额是固定测试数据。
2. 运行测试
在项目目录执行:
python test_unit5_lesson10_idempotency.py
如果上一课的保存函数保持原样,预期看到四个测试都显示 ok,最后类似:
Ran 4 tests in ...s
OK
含义是:这四个测试场景通过了。
如果出现:
FAIL:运行结果不符合断言。ERROR:测试过程中出现了未预期的异常。
3. 看懂三个主要写法
① setUp():每个测试开始前准备环境
def setUp(self):
它不是整个文件只执行一次,而是每个 test_... 方法开始前执行一次。因此四个测试使用四份独立的临时数据库,不依赖执行顺序。Python 测试框架说明
② assert...:把预期写成检查
self.assertEqual(first_id, second_id)
意思是"两个编号必须相同"。
self.assertFalse(second_created)
意思是"第二次不能新建记录"。
with self.assertRaises(ValueError):
self.save(quantity=3, total_yuan="121.50")
意思是"这里应该抛出 ValueError"。如果没有抛出,测试反而失败。
③ patch.object():临时替换配置
patch.object(orders, "DB_PATH", self.db_path)
它临时把保存函数使用的数据库路径换成测试路径,不会修改上一课的源文件 。测试清理时恢复原值。官方说明
这里仍然执行真实的 SQLite 写入,只是写入临时数据库,并没有把保存函数换成假的。
4. 为什么不直接让模型测试?
因为我们要验证的是一个确定的业务规则:
同一个请求是否重复保存?
这件事可以直接调用函数,检查返回值和数据库记录,不需要经过模型。这样测试更快,也不会受模型措辞或工具选择波动影响。
但这四个测试还没有覆盖:
- 模型是否选择正确的工具。
- 人工拒绝后是否确实不写入。
- 参数校验是否正确。
- 并发请求、断电或跨进程故障。
因此,OK 表示这些已写出的场景通过,不能理解成整个助手已经没有问题。
本课记住:模型行为需要评估,确定的业务逻辑则应该用自动化测试检查。