- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
本文基于开源仓库 nodebestpractices(The Node.js best practices list)中 「按 AAA 模式组织测试结构」 一文展开。它以"如何写出让读者一眼看懂意图的测试"为核心,完整讲解 AAA(Arrange-Act-Assert)三阶段的定义、结构化示例与反模式对比,并延伸关联测试命名、测试报告可读性等相邻实践。读完本文,你将掌握 AAA 模式的落地写法、判断测试是否"过度复杂"的标准,以及一套能让测试报告像需求文档一样可读的方法。
为什么测试代码必须"简单到极致":给读者留出脑力预算
我们做测试时面临的最大挑战,不是技术难度,而是脑力空间(headspace)的匮乏——生产代码本身已经让团队忙得不可开交,留给测试代码的"心智预算"非常有限。因此,测试代码必须保持"死简单"(dead-simple)且易于理解。
这意味着:当一个人阅读测试用例时,它不应该像在读命令式代码(循环、继承、控制流交织),而应该更像在读 HTML——一种声明式体验:一眼扫过,意图自现。正如仓库 README.md 中对该条目的 TL;DR 总结所言:
TL;DR:用三个清晰分离的段落来组织测试:Arrange、Act 与 Assert(AAA)。第一部分包含测试设置,然后是执行被测单元,最后是断言阶段。遵循这一结构可以保证读者无需耗费任何"大脑 CPU"就能理解测试计划。
而如果做不到这一点,反噬是直接的:你每天不仅要花大量时间理解主代码,现在连本该是"一天中最简单环节"的测试,也在继续消耗你的脑力。
AAA 模式:三个 A 的精确定义
AAA 模式要求把每个测试用例拆成三个职责清晰的阶段。仓库文档给出了精确定义:
第一个 A —— Arrange(准备):所有用于把系统带到"测试所期望模拟的场景"的设置代码。它可能包括:实例化被测单元的构造函数、向数据库添加记录、对对象做 mock/stub,以及任何其他准备性代码。
第二个 A —— Act(执行):执行被测单元(unit under test)。通常只有1 行代码。
第三个 A —— Assert(断言):确保接收到的值与预期相符。通常也只有1 行代码。
这种"阶段分离"并非 AAA 独有。文档指出,还存在其他类似格式,例如 XUnit 模式的"Setup(准备)、Exercise(执行)、Verify(验证)、Teardown(拆除)"四阶段测试;日常 TDD 中常见的 Given/When/Then(给定/当/那么)也与之同源。而AAA 模式最早由 Bill Wake 观察并命名(见 英文版文档 的引用来源说明),如今已成为 JavaScript/Node.js 测试社区中最普及的书写约定。
代码示例:一个按 AAA 模式结构化的测试
以下示例完整取自仓库文档:使用 Jest 的describe/test语法与sinon做数据访问层 stub,测试"客户分类器"将高消费客户归类为 premium 的逻辑:
describe.skip("客户分类器", () => { test("当客户消费超过 500 美元时,应被归类为 premium", () => { // Arrange(准备) const 待分类客户 = { 消费额: 505, 注册时间: new Date(), id: 1 }; const 数据库Stub = sinon .stub(dataAccess, "获取客户") .reply({ id: 1, 分类: "普通" }); // Act(执行) const 收到的分类 = 客户分类器.分类客户(待分类客户); // Assert(断言) expect(收到的分类).toMatch("premium"); }); });观察这个用例的结构价值:
- Arrange 阶段清晰交代了测试场景的前提:构造一个消费 505 美元(超过阈值 500)的客户对象,并用
sinon.stub替换dataAccess.getCustomer的真实调用,让数据访问层返回"普通客户"记录——这正是"准备"阶段要完成的依赖隔离与场景搭建; - Act 阶段只有一行:调用被测的
classifyCustomer方法; - Assert 阶段也仅一行:断言分类结果包含
"premium"。
describe.skip表明这组用例当前被跳过(常用于先写骨架、后补齐实现的场景),但结构示范本身完整保留。测试名也遵循了"场景 + 期望"的可读性要求,与仓库中 「测试名应包含 3 个部分」 的最佳实践互相呼应。
反模式:没有分离、一个大杂烩、更难解读
同样是这段测试逻辑,如果放弃 AAA 的三段式分隔,把全部代码揉进一个测试函数里,可读性立刻崩塌:
test("应被归类为 premium", () => { const 待分类客户 = { 消费额: 505, 注册时间: new Date(), id: 1 }; const 数据库Stub = sinon .stub(dataAccess, "获取客户") .reply({ id: 1, 分类: "普通" }); const 收到的分类 = 客户分类器.分类客户(待分类客户); expect(收到的分类).toMatch("premium"); });两段代码在功能上完全等价,但反模式版本存在三个问题:
- 意图不可见:没有注释分区,读者必须逐行阅读并自行推断"哪行是准备、哪行是执行、哪行是断言";
- 测试名失语:
应被归类为 premium没有说明被测对象、场景(消费多少)与触发条件,读者必须读完整段代码才能猜出测试在验证什么行为; - 心智成本高:这正是 README 中 "Otherwise" 警告的场景——本应简单的一天中的测试环节,却持续消耗大脑。
AAA 的价值不在于改变测试逻辑,而在于用物理上的结构分隔,把作者的意图直接"传译"给读者。
让测试报告像需求文档:测试名的 3 部分与测试的 6 部分
AAA 只是可读性的一部分。仓库文档引用 Yoni Goldberg 的博客观点提出:"每个测试应包含 6 个部分"——一份优秀的测试报告应当向并非熟悉代码的人(测试人员、负责部署的 DevOps 工程师、以及两年后的你自己)直接说明:当前应用版本是否满足需求。
测试报告要"以需求的语言说话",其名称应包含 3 个部分(详见 3-parts-in-name.md):
- 测的是什么?例如
ProductsService.addNewProduct方法; - 在什么场景/条件下?例如"没有传入价格";
- 期望结果是什么?例如"新产品不被批准"。
当测试名以"被测对象 + 场景 + 期望"三段式书写,并在函数体内配以 AAA 的三段式结构时,测试报告就接近于一份可读的需求文档。下图即仓库文档所配的测试报告示例(每个测试包含 6 个部分):
实战技巧:把 Assert 放在最先写
对新手来说,"Arrange 先写"看似自然(因为它排在第一位),但仓库 英文版文档 引述了来自 Bill Wake 的实战技巧(该技巧源自 Jim Newkirk):
从哪里开始?你可能会认为 Arrange 是自然应该先写的部分,因为它排在前面。当我在系统地梳理一个对象的各个行为时,我可能会先写 Act 那一行。 但我从 Jim Newkirk 那里学到的有用技巧是:先把 Assert 写出来是最好的起点。当你确定要测试一个新行为时,Assert-First 让你从"假如它能工作,我该怎么判断?"开始。断言就位后,就可以采用业界所称的 "Frame First" 方式,借助 IDE 的智能提示来"填空"。
这意味着:先声明"我想要什么结果",再倒推出执行与被测场景,最后补齐准备代码——断言先行天然倒逼你明确行为的验收标准,避免"为了写测试而写测试"。
为什么 AAA 值得成为全团队的统一约定
仓库文档还引述了《Unit Testing: Principles, Practices, and Patterns》一书的核心观点:
3A 模式很简单,并为测试套件中的所有测试提供了统一的结构。这种统一结构正是它最大的优势之一:一旦你习惯了这一模式,就能更容易地阅读和理解测试。反过来,这也会降低整个测试套件的维护成本。
统一结构带来的收益是系统性的:新成员加入团队时不需要逐个项目摸索测试写法;Code Review 时审阅者可以快速定位"准备/执行/断言"的边界;重构测试时改动范围清晰可控。这与仓库中其他测试条目(如 避免全局测试夹具、隔离测试中间件、Mock 外部服务)共同构成了"让测试既可读又可信"的完整方法论。
引用与延伸阅读
该条目引用了业界经典资料的思想来源,包括:
- XUnit Patterns(四阶段测试)关于测试可读性的论述:让测试读者能够快速确定测试正在验证哪种行为——被测系统(SUT)的多种行为被依次调用时,有些用于搭建测试前状态(fixture)、有些用于执行 SUT、有些用于验证测试后状态,清晰标识这四个阶段会让测试意图变得更容易看出;
- Bill Wake对 AAA 模式的首次观察与命名(Arrange, Act, Assert);
- Yoni Goldberg的 "30 Node.js testing best practices" 系列("每个测试包含 6 个部分")。
若想继续深入,推荐按以下顺序阅读仓库内关联条目:
- AAA 条目英文版(本主题的规范表述)
- README 4.2 测试名 3 部分 与 3-parts-in-name.md(测试命名规范)
- test-five-outcomes.md(测试应覆盖的 5 类结果:响应、新状态、外部调用、消息队列、可观测性)
- avoid-global-test-fixture.md(测试数据隔离)
- mock-external-services.md(外部服务替身策略)
总结:AAA 不是新奇的技巧,而是 Node.js 测试代码的"排版规范"——它用 Arrange、Act、Assert 三个物理分区,把测试从"命令式代码"升级为"声明式文档"。配合三段式测试名与"每测试 6 部分"的报告意识,你的测试套件将不再只是回归守护网,而是一份随时可读、可维护、可交接的活需求文档。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
用 Arrange/Act/Assert 组织单元测试:java-design-patterns 中 Cash 示例的 AAA 测试模式实战指南
用 Arrange/Act/Assert 组织单元测试:java design patterns 中 Cash 示例的 AAA 测试模式实战指南 本文以 jav
示例工程教程Quick框架中的测试模式:Arrange-Act-Assert最佳实践
Quick框架中的测试模式:Arrange Act Assert最佳实践 引言 在软件开发中,单元测试是保证代码质量的重要手段。Quick作为Swift生态中流
测试开发工具Arrange/Act/Assert(AAA)测试模式实战:用三段式结构写出清晰可维护的 Java 单元测试
Arrange/Act/Assert(AAA)测试模式实战:用三段式结构写出清晰可维护的 Java 单元测试 本文以 java design patterns
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考