第二十九章 测试体系
篇别:第五篇 调试与测试
本章学习目标
- 编写
TransactionCase/SavepointCase及@tagged策略。 - 了解 QUnit / Hoot 等 前端单测 入口与定位官方样例的方法。
- 使用
HttpCase做 路由与会话 级集成测试。 - 建立 性能基线 意识(查询次数、耗时),与第三十一章衔接。
导读:测试是「可执行的规格」
手工点界面无法 回归 每个版本。Python 测试 覆盖 ORM、约束、规则;HttpCase 覆盖 Controller 与 Cookie;前端测试 覆盖 OWL 组件逻辑。本章给出 最小可运行模式 与 选型表;细节以 odoo.tests 与官方 Testing 文档为准。
29.1 Python 单元测试
29.1.1 知识要点
- 基类:
TransactionCase每测例 回滚事务;SavepointCase在 savepoint 内更快回滚;HttpCase带 HTTP 客户端。 - 标签:
@tagged('post_install', '-at_install')控制 安装阶段是否运行;CI 常 跳过 at_install 以提速。 - 环境:
self.env;cls.env.user在setUpClass中准备 多公司 / 多用户 场景。 - 断言:
assertEqual、assertRaises;业务上多断言 记录规则(with self.assertRaises(AccessError))。
29.1.2 案例
from odoo.tests import TransactionCase, tagged
from odoo.exceptions import ValidationError
@tagged("post_install", "-at_install")
class TestLibrary(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.Book = cls.env["library.book"]
def test_create_book(self):
b = self.Book.create({"name": "T", "isbn": "9780000000000"})
self.assertTrue(b.id)
def test_isbn_unique_constraint(self):
self.Book.create({"name": "A", "isbn": "9781111111111"})
with self.assertRaises(ValidationError):
self.Book.create({"name": "B", "isbn": "9781111111111"})
29.1.3 截图占位

29.1.4 本节练习
- 实操:为 「ISBN 唯一」 约束补 失败用例(见上例)。
- 简答:
at_install与post_install各适合测什么?
参考答案提示:2. at_install:模块结构、快速语法;post_install:依赖模块数据已存在时的业务逻辑。
29.2 JavaScript 单元测试(补充)
29.2.1 知识要点
- Odoo 前端测试框架随版本演进(QUnit → 新套件);以
web模块内测试 与 官方文档 为准。 - OWL 组件:mount 到 fixture,mock service(
orm、notification),断言 DOM / state。 - 定位样例:在
addons/web/static/tests(路径以实际源码树为准)搜索mount、getFixture。
29.2.2 案例(思路)
- 复制 官方最小字段组件测试 结构。
- 替换为你的 组件类 与 模板。
- mock
orm.searchRead返回固定列表,断言渲染文本。
29.2.3 截图占位

29.2.4 本节练习
- 拓展:在源码树中找到 一个 官方 OWL 测试文件路径并记录。
- 简答:前端测试 不能 替代哪些 后端测试?
参考答案提示:2. ACL、规则、约束、并发写入、SQL 注入防护等。
29.3 集成测试
29.3.1 知识要点
HttpCase:self.url_open('/web/login')、authenticate;可测 CSRF、路由、重定向。- 浏览器 E2E:可选 Playwright / Selenium 外挂;维护成本高,宜 少量关键路径。
- 邮件 / 外部 API:
mock.patch或 Odoo 邮件测试助手(mail模块工具),避免 真实外发。
29.3.2 案例
from odoo.tests import HttpCase, tagged
@tagged("-at_install", "post_install")
class TestLibraryHttp(HttpCase):
def test_public_page_ok(self):
self.authenticate("admin", "admin")
res = self.url_open("/web")
self.assertEqual(res.status_code, 200)
29.3.3 截图占位

29.3.4 本节练习
- 简答:
SavepointCase与TransactionCase选型? - 实操:写一个 需登录 才能访问的 controller,用 HttpCase 分别测 未登录 403/302 与 已登录 200。
参考答案提示:1. SavepointCase:大量相关用例共享昂贵 setUp 时更快;否则 TransactionCase 简单直接。
29.4 性能测试(新增)
29.4.1 知识要点
- 查询计数:Odoo 提供 测试工具 断言 SQL 次数(API 以当前版本为准,常见为
assertQueryCount或 profiler 钩子)。 - 基线:在 CI 中记录 主路径查询次数,回退即失败(允许小幅浮动需 团队约定)。
- 瓶颈:N+1 优先用
search_fetch/ prefetch 修(见第三十一章)。
29.4.2 案例(概念)
# 伪代码:名称以 odoo.tests 实际 API 为准
# with self.assertQueryCount(...):
# records.do_something_heavy()
实施前在目标版本 shell 中 help(odoo.tests) 或查 官方 Testing 文档确认 准确装饰器名。
29.4.3 截图占位

29.4.4 本节练习
- 实操:选一业务方法,用 日志或 profiler 记录 冷启动 SQL 次数 作为 口头基线。
- 简答:性能测试为何要 固定随机种子 / 数据量?
参考答案提示:2. 否则基线漂移,误报/漏报。
本章综合练习
- CI 矩阵:Odoo 版本 × Python 版本 最小组合建议(自洽说明一条理由)。
- 数据:测试数据用
setUpClass批量创建 还是 fixture XML?各举 一利一弊。 - 综合:某用例在 本地绿、CI 红,列出 四条 排查方向(时区、标签、依赖模块、演示数据)。
- 安全:测试中
sudo()使用原则?
参考答案提示:4. 仅用于 搭建数据;断言规则时应用 普通用户 上下文。
本章对应白皮书目录:第二十九章 测试体系。