Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

principle-test-behavior-not-implementation

测行为,不测实现

A test calls the code the way its users do and asserts the result they observe against a literal expected value. A test that asserts which calls the code made, or restates a constant the code contains, does neither.

测试按用户方式调用代码,并用字面期望值断言他们观察到的结果。断言代码做了哪些调用,或复述代码里某个常量的测试,两样都没做。

The check: before you keep a test, ask whether it would still pass if every function it imports returned undefined. If yes, it observes no behavior and cannot fail for a defect. Rewrite the assertion or delete the test.

自检:保留测试前,问若它 import 的每个函数都返回 undefined,测试是否仍过。是,就观察不到行为,也不会因缺陷失败。改断言或删测试。

Why: A test that cannot fail for a defect costs CI time and review attention and catches nothing. A constant pin also fails when someone edits the constant or the prompt it restates, so it prevents that edit.

为什么: 不能因缺陷失败的测试白耗 CI 时间和审阅注意力,什么也抓不到。钉死常量也会在有人改常量或它复述的 prompt 时失败,从而阻止那次编辑。

Five shapes that still pass when every imported function returns undefined:

在每个 import 函数返回 undefined 时仍会过的五种形状:

  • Weak or no assertion. No expect, or only toBeDefined, toBeTruthy, not.toThrow, toBeInstanceOf, toBeGreaterThan(0).

  • Mock or absence only. Only toHaveBeenCalled, not.toHaveBeenCalled, toBeUndefined, toEqual([]), toHaveLength(0), not.toBe(wrongValue).

  • Self-referential. The expected value comes from the code under test: expect(f(a)).toBe(f(a)), expect(parsed.url).toBe(buildUrl(...)).

  • Constant pin. The assertion restates a hand-maintained constant, config default, table row, or prompt string: expect(LIMITS.maxTools).toBe(8), expect(PROMPT).toContain("You are").

  • Fixture asserts fixture. The assertion reads data the test built or a value computed in beforeEach, and the subject never runs inside the body.

  • 弱断言或无断言。 没有 expect,或只有 toBeDefined、toBeTruthy、not.toThrow、toBeInstanceOf、toBeGreaterThan(0)。

  • 只测 mock 或缺失。 只有 toHaveBeenCalled、not.toHaveBeenCalled、toBeUndefined、toEqual([])、toHaveLength(0)、not.toBe(wrongValue)。

  • 自指。 期望值来自被测代码:expect(f(a)).toBe(f(a))、expect(parsed.url).toBe(buildUrl(...))。

  • 钉死常量。 断言复述手维护常量、配置默认值、表行或 prompt 字符串:expect(LIMITS.maxTools).toBe(8)、expect(PROMPT).toContain("You are")。

  • fixture 断言 fixture。 断言读的是测试自己建的数据或 beforeEach 算出的值,被测主体在 body 里根本没跑。

The fix: call the subject inside the test body with one concrete input and assert the literal output or the observable effect, expect(slugify("Hello, World!")).toBe("hello-world"). For an absence, assert the presence on the other input in the same test. For a constant, test the mechanism that reads it with one input instead of restating the value. For a mock, assert the payload it received or the state after the call, not that it was called. When no such assertion exists, delete the test.

修法: 在测试 body 里用一个具体输入调用主体,断言字面输出或可观察效果,如 expect(slugify("Hello, World!")).toBe("hello-world")。测缺失时,同测里断言另一输入上的存在。测常量时,测读取它的机制加一个输入,别复述值。测 mock 时,断言它收到的载荷或调用后状态,别断言「被调用过」。没有这种断言就删测试。

Keep a test of a relation across a table’s rows (a key present in two tables, a parent that exists), and a compile-time check in a *.test-d.ts file.

保留跨表行关系的测试(两表都有的 key、存在的 parent),以及 *.test-d.ts 里的编译期检查。