第五单元——第10课:用自动化测试验证工具逻辑

上一课验证了"重复请求不会重复保存"。但以后修改代码,怎样知道这个功能没有被改坏?

这节把人工观察变成自动检查:

  1. 相同请求只保存一条记录。
  2. 同一请求编号不能更换内容。
  3. 不同请求可以分别保存。
  4. 不同用户的请求不会相互混淆。

不调用模型,不消耗 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 表示这些已写出的场景通过,不能理解成整个助手已经没有问题。

本课记住:模型行为需要评估,确定的业务逻辑则应该用自动化测试检查。

相关推荐
小小AK1 小时前
生产入库单同步实战:MySQL 到金蝶云星空的带料委外场景拆解
数据库·mysql
凤凰院凶涛QAQ1 小时前
《MySQL内核拆解:从SQL到InnoDB》(一) : 数据库基础&库操作&数据类型
数据库·sql·mysql
360智汇云1 小时前
隔离VPC标准路径:出公网链路的设计与实现
android·服务器·数据库
紫金桥软件1 小时前
紫金桥国产组态软件:安全稳定护航工业现场
数据库·自动化·跨平台·scada·组态软件
qq_436962181 小时前
金蝶云星空数据分析怎么做?
java·数据库·数据分析
gz927cool2 小时前
在 Harness 上搭建 Agent-Native 应用:可迁移经验
服务器·人工智能·ai·系统架构
坐吃山猪2 小时前
ChromaDB09-数据库操作
数据库·chromadb
感谢地心引力2 小时前
Gemini 4 Argon 使用 REA 分析 Rstudio,实现简体中文支持
ai·逆向·r·汉化·rstudio·rea
IntoTech英途汇骏2 小时前
数据虚拟化平台选型分析:主流厂商解决方案与技术能力对比
数据库