Writing Unit Tests

作者 riekelte67b7af9ac74無授權條款5 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫3 週前更新

Use when writing or refactoring unit tests - a new test file, added cases, a flaky test, an unreadable one. Encodes behavior-first testing: one behavior per test, names that state the claim, deterministic setup, mocks only at boundaries you do not own. Use whenever a test is being written, even a quick one, and whenever a test needs a sleep, a mock of your own code, or a copy of the implementation's math.

AI 產生的概覽

指導撰寫與重構單元測試,遵循行為優先原則:每個測試一個行為、確定性設定、僅在邊界處使用模擬。

功能
此技能提供撰寫與重構單元測試的指引。內容涵蓋透過公開契約進行測試、以行為聲明命名測試、保持測試確定性(不依賴真實時間、隨機性、網路或檔案系統),以及僅對團隊不擁有的邊界進行模擬。它也列出常見錯誤,例如鏡像測試、模擬回聲室、巨型測試,以及用睡眠掩蓋不穩定。
適用情境
適用於建立新測試檔案、新增測試案例、修復不穩定測試或清理難以閱讀的測試。只要在撰寫單元測試時都適用,即使是快速測試;也適用於測試需要睡眠、模擬自己的程式碼或複製實作中的數學計算時。
執行需求
不需要指令碼或工具;這是僅含說明的技能。它引用 principal-engineering 和 testing-changes 技能作為必備背景。

Writing unit tests

REQUIRED BACKGROUND: the principal-engineering skill. testing-changes governs which tests a change owes; this skill is the craft of the tests themselves.

Overview

A unit test is a behavioral claim with a name, read by the next engineer during a red build. Core principle: test the contract, not the implementation. The name carries the claim, and the test stays simple enough that it cannot itself be wrong.

Contract over implementation

  • Test through the public contract of the unit. A refactor that preserves behavior should not break tests; when it does, the tests were asserting the implementation.
  • Do not assert call sequences, internal state, or that method A called method B, unless the interaction IS the contract (a required side effect on a boundary).
  • Never derive the expected value from the production arithmetic, neither by reimplementing the formula nor by invoking the shared helper that computes it. Expected values are literals worked out independently (by hand, from a spec, from real data), with the derivation in a comment.

One behavior per test, named as the claim

  • One behavior per test; splitting is cheaper than archaeology on a multi-assert failure.
  • The name states subject, scenario, and expected outcome: expired_token_is_rejected_with_401, not test_auth_3. Test names describe behavior, state transitions, and invariants; never delivery order, ticket keys, or phases.
  • Arrange, act, assert, visibly and in that order. No branching, loops, or logic in a test: a test with logic needs its own test. Shared setup earns a builder or a role-named fixture; a mystery blob fixture hides which arranged fact the assertion depends on. Generation and iteration live in builders and helpers, not in the test body. Property-based tests are the accepted form for invariants and follow their framework's shape; example-based tests stay logic-free.

Determinism

  • No real time, real randomness, real network, or real filesystem inside a unit test: inject the clock, seed or inject the randomness, mock the boundary.
  • No sleeps. Waiting for async work is condition-based (poll the observable outcome with a deadline), never duration-based.
  • A flaky test is red: fix it or quarantine it visibly with an owner (see the red-test rule in testing-changes); re-running until green is silencing a detector.

Mocks are assumptions

  • Mock the boundaries you do not own (network, clock, filesystem, third-party services); prefer real collaborators for code you do own within the unit's reach. For owned wrappers around unowned resources (your repository class fronting the database), mock at the seam where owned code last touches the unowned resource, and keep the test data role-named and visible either way. Every mock hardcodes an assumption about a contract; a stale mock is how a suite stays green while the real integration is broken.
  • When a test is mostly mock wiring, it is testing the mocks. Either widen the unit to something with real behavior or accept that this seam needs an integration test instead (and say which).
  • Fixtures are labeled snapshots of reality: minimal, role-named for their part in the scenario, updated deliberately when the contract changes, never regenerated blindly to make red go green.

Assertions and failure paths

  • Assert outcomes with values, not absence of exceptions. "It did not throw" claims almost nothing.
  • Failure paths are first-class test subjects: the typed failure surfaces, the degraded mode is entered loudly, the guard actually guards (see handling-failures).
  • Tests themselves follow the no-silent-swallows contract: no catch-and-ignore in test code, no conditional assertions that skip silently when a precondition is absent. A test that cannot run must fail or be visibly skipped with the reason.

Common mistakes

  • The mirror test: reimplementing the production logic to compute the expectation.
  • The mock echo chamber: mocking your own class and asserting the mock.
  • The mega-test: twelve assertions, one name, no way to know which claim broke.
  • Shared mutable fixtures that make test order matter; every test builds or receives its own state.
  • The sleep that "fixes" flakiness by making it rarer.
  • Green-checking the fixture: editing expected values to match actual output without deriving why the new value is right.

來源與署名

來源:riekelt/principal-engineer位於plugins/principal-engineer/skills/writing-unit-tests提交e67b7af

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架