testing-strategy

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing Strategy

测试策略

Tests are the only correctness instrument you control at build time: shape the suite to the code, not to a decades-old ratio, and make each test assert behaviour a build either passes or fails. Applies to every test under
test/
and
integration_test/
.
Read the reference for the task at hand:
  • references/test-layers.md
    — per-layer harness, imports, teardown, edge tables (pure core, in-memory Drift, headless Notifier, integration).
  • references/property-and-fakes.md
    — fakes-over-mocks, enum-driven fake state, seeded fuzz, independent oracles, absence-of-a-failure-class tests.
  • references/coverage-and-budget.md
    — file-level floors, the coverage-lies-upward fix, the suite-time budget, and the manual-pass handoff.
Run
scripts/check_test_hygiene.sh
and
scripts/run_tests.sh
before a PR.
Golden, RTL, and a11y widget mechanics live in
widget-golden-and-a11y-testing
; this skill governs everything below the pixel.
测试是你在构建时唯一能够掌控的正确性工具:让测试套件随代码塑形, 而不是套用几十年前的固定比例,并让每个测试都断言一种构建要么通过、 要么失败的行为。适用于
test/
和
integration_test/
下的每个测试。
针对当前任务阅读参考文档:
  • references/test-layers.md
    — 各层测试 harness、imports、teardown、边界表(纯 core、内存 Drift、无头 Notifier、integration)。
  • references/property-and-fakes.md
    — 优先 fake 而非 mock、enum 驱动的 fake state、带种子的 fuzz、独立 oracle,以及验证某类失败不会发生的测试。
  • references/coverage-and-budget.md
    — 文件级下限、
    coverage-lies-upward
    修复、测试套件时间预算,以及 manual pass 交接。
提交 PR 前运行
scripts/check_test_hygiene.sh
和
scripts/run_tests.sh
。
Golden、RTL 和 a11y 的 widget 机制归
widget-golden-and-a11y-testing
; 本 skill 管辖像素层以下的所有内容。

Non-negotiable rules

不可协商的规则

  1. Shape the suite to the code, not the pyramid. The 70/20/10 numbers trace to a 2011 test-size heuristic (its author said they were "pulled out of a hat") and never described Flutter's unit/widget/integration taxonomy. Test at the cheapest tier that can assert the behaviour: anything expressible as
    f(input) -> output
    is a unit or property test, never a
    pumpWidget
    . Driving pure logic through the widget tree is slower, flakier, and hides which layer broke.
  2. Put business rules in a Flutter-free package and inject a
    Clock
    .
    Domain math lives in pure Dart with zero Flutter/plugin/IO imports, tested with
    package:test
    (not
    flutter_test
    ). The one time type is
    package:clock
    's
    Clock
    — NEVER
    DateTime.now()
    , never a bespoke
    ClockService
    . Pure
    core
    reads the ambient
    clock.now()
    , pinned in tests with
    withClock(Clock.fixed(t), …)
    ; Riverpod/feature code reads the injected
    clockProvider
    , overridden with
    clockProvider.overrideWithValue(Clock.fixed(t))
    (seam owned by
    value-objects-money-and-units
    ). The ban is structural: a pure package declares no Flutter SDK constraint.
  3. Assert invariants, not just examples. Every conversion has a round-trip test (
    decode(encode(x)) == x
    ) and rounding goldens at half-way/boundary values; every universal claim is a seeded fuzz loop (
    for (var seed = 0; seed < N; seed++)
    ) or property test, checked against an independent oracle — never the production function under test. Print the generated input in
    reason:
    so a failure is its own minimal repro.
    ==
    on integers only;
    closeTo(x, 1e-9)
    on doubles.
  4. Test the data layer against a real in-memory engine. Use
    NativeDatabase.memory()
    , never a mocked DAO — a mocked DAO proves nothing about SQL, constraints, indexes, or migrations. Mock repositories only above the data layer.
    addTearDown(db.close)
    and close streams synchronously so reactive
    .watch()
    streams do not leak "Timer still pending".
  5. Prefer bare-
    implements
    fakes over mocks for code you own.
    A
    class FakeX implements X
    (no
    noSuchMethod
    superclass) makes an interface change a compile error, models the risk (state, not call-order) as a field, and doubles as the contract's documentation. Reserve
    mocktail
    for genuinely external dependencies; never
    mockito
    /
    @GenerateMocks
    (codegen, no null-safety win).
  6. registerFallbackValue
    for every custom type passed to
    any()
    /
    captureAny()
    in
    setUpAll
    . mocktail throws at runtime, not compile time, on a missing fallback — this is the single most common mocktail failure.
  7. Drive Notifiers headlessly with
    ProviderContainer
    .
    Override provider dependencies via
    overrideWith
    ; never pump a widget to test state. Assert on the exposed
    AsyncValue
    /state, drive actions through
    .notifier
    , and dispose the container in teardown.
  8. Guard one end-to-end acceptance gate. A single realistic scenario asserted to the exact expected result, plus the conservation invariant (parts sum to the whole). It is the test that proves the pieces compose. Back it with a runtime assert tripwire inside the primitive itself (
    assert(sum(result) == total)
    ) — free in release, catches the bug the moment it happens.
  9. Hold an unrecoverable-bug-files floor by diff-review, not a coverage gate. There is no automated percentage gate — coverage is a published report (
    ci-pipeline-and-gates
    owns that). Instead hold a 100% floor on the handful of files (migrations, the money/allocate primitive, the parser with wire-format traps) where a gap is silent data loss — enforced by reading the diff, not by counting lines. A directory or global percentage gate rewards vanity tests and exclusion churn. First fix the coverage-lies-upward gap:
    flutter test --coverage
    omits files no test imports, so the number overstates safety.
  10. Never
    pumpAndSettle()
    on an indefinite animation
    (splash, shimmer, spinner) — it hangs on a 10-minute timeout. Use timed
    pump(Duration)
    with
    fakeAsync
    .
  11. Keep the suite fast, and hand structurally-untestable paths to a manual pass. A suite that costs minutes gets skipped, and a skipped suite is a distrusted one. Anything an emulator cannot reproduce (real audio, OEM device diversity, native surfaces without a Flutter engine) is enumerated in a named manual pre-release pass, not faked green — a green test that proves nothing is worse than an admitted gap.
  1. 按代码塑形测试套件,而不是遵循金字塔。 70/20/10 这一比例可追溯至 2011 年的一项 test-size 启发法(其作者称这些数字是“随手编出来的”),且从未描述 Flutter 的 unit/widget/integration 分类体系。应在能够断言该行为的成本最低层级进行测试:任何可表达为
    f(input) -> output
    的逻辑都应使用 unit test 或 property test,绝不能通过
    pumpWidget
    驱动。通过 widget tree 驱动纯逻辑不仅更慢、更易波动,还会掩盖究竟是哪个层出现了问题。
  2. 将业务规则放在不依赖 Flutter 的 package 中,并注入
    Clock
    。
    领域计算应位于纯 Dart 中,不引入任何 Flutter/plugin/IO imports,并使用
    package:test
    测试(而不是
    flutter_test
    )。唯一的时间类型是
    package:clock
    的
    Clock
    ——绝不使用
    DateTime.now()
    ,也不创建定制的
    ClockService
    。纯
    core
    读取环境中的
    clock.now()
    ,测试中通过
    withClock(Clock.fixed(t), …)
    固定时间;Riverpod/feature 代码读取注入的
    clockProvider
    ,并通过
    clockProvider.overrideWithValue(Clock.fixed(t))
    覆盖它(该 seam 由
    value-objects-money-and-units
    负责)。这是结构性禁令:纯 package 不得声明 Flutter SDK constraint。
  3. 断言不变量,而不只是断言示例。 每种转换都必须有 round-trip 测试(
    decode(encode(x)) == x
    ),并在半程值和边界值上进行 rounding golden 测试;每项普适性断言都必须采用带种子的 fuzz 循环(
    for (var seed = 0; seed < N; seed++)
    )或 property test,并对照独立 oracle 检查——绝不能使用被测的生产函数本身。将生成的输入打印在
    reason:
    中,使每次失败本身都能成为最小复现。整数仅使用
    ==
    ;double 使用
    closeTo(x, 1e-9)
    。
  4. 使用真实的内存引擎测试数据层。 使用
    NativeDatabase.memory()
    ,绝不使用 mocked DAO——mocked DAO 无法证明 SQL、constraints、indexes 或 migrations 的任何行为。只有在数据层之上才应 mock repositories。调用
    addTearDown(db.close)
    ,并同步关闭 streams,避免响应式
    .watch()
    streams 泄漏 “Timer still pending”。
  5. 对自己拥有的代码,优先使用 bare-
    implements
    fake,而非 mock。
    class FakeX implements X
    (不使用
    noSuchMethod
    superclass)会在接口发生变化时产生 compile error,并以字段建模风险(状态而非调用顺序),同时还能作为契约文档。只将
    mocktail
    用于真正的外部依赖;绝不使用
    mockito
    /
    @GenerateMocks
    (依赖 codegen,对 null-safety 没有额外帮助)。
  6. 在
    setUpAll
    中,为传给
    any()
    /
    captureAny()
    的每种自定义类型调用
    registerFallbackValue
    。
    缺少 fallback 时,mocktail 会在运行时而不是编译时抛错——这是最常见的 mocktail 失败原因。
  7. 通过
    ProviderContainer
    无头驱动 Notifiers。
    使用
    overrideWith
    覆盖 provider dependencies;绝不能为了测试状态而 pump widget。对暴露的
    AsyncValue
    /state 发起断言,通过
    .notifier
    触发 action,并在 teardown 中 dispose container。
  8. 设置一个端到端验收门禁。 使用一个贴近现实的场景,并断言其精确的预期结果,同时验证守恒不变量(各部分之和等于总量)。这是证明各部分能够正确组合的测试。还应在基础原语内部设置运行时 assert 绊线(
    assert(sum(result) == total)
    )——在 release 构建中零成本,并能在问题发生的瞬间捕获 bug。
  9. 通过 diff review 守住不可恢复 bug 文件的下限,而不是使用 coverage gate。 不设置自动化百分比门禁——coverage 是一份已发布的报告(由
    ci-pipeline-and-gates
    负责)。相反,应对少数几个一旦存在测试缺口就会造成静默数据丢失的文件(migrations、money/allocate 基础原语、带 wire-format 陷阱的 parser)保持 100% 下限,并通过阅读 diff 而不是统计代码行来强制执行。目录级或全局百分比门禁只会奖励虚荣测试,并造成 exclusions 的频繁变动。首先修复
    coverage-lies-upward
    缺口
    :
    flutter test --coverage
    会忽略没有任何测试导入的文件,因此该数字会高估安全性。
  10. 绝不对无限持续的动画(splash、shimmer、spinner)调用
    pumpAndSettle()
    ——它会一直卡住,直到 10 分钟超时。使用定时
    pump(Duration)
    配合
    fakeAsync
    。
  11. 保持测试套件快速,并将结构上无法测试的路径移交给人工检查。 耗时数分钟的测试套件会被跳过,而被跳过的测试套件也就不再值得信任。任何模拟器无法复现的内容(真实音频、OEM 设备差异、没有 Flutter engine 的 native surfaces)都应明确列入有名称的 manual pre-release pass,而不是伪造为通过——一个无法证明任何事情的绿色测试,比坦承存在的缺口更糟糕。

Tier the test to the code (
package:test
vs
flutter_test
)

根据代码选择测试层级(
package:test
vs
flutter_test
)

Pure logic runs under
dart test
with no widget binding; only UI and plugin-wrapper tests need
flutter_test
.
dart
// test/domain/money_test.dart — pure Dart, `dart test`, no flutter_test import.
import 'package:test/test.dart';
import 'package:app_core/app_core.dart';

void main() {
  // Round-trip: encoding is lossless across the ISO-4217 exponent range.
  test('minor-units round-trip for exponents 0, 2, 3', () {
    for (final currency in [Currency.jpy, Currency.usd, Currency.kwd]) {
      final money = Money(amountMinor: 12345, currency: currency);
      expect(Money.parse(money.format(), currency), equals(money));
    }
  });
}
纯逻辑应在
dart test
下运行,不使用 widget binding;只有 UI 和 plugin-wrapper 测试才需要
flutter_test
。
dart
// test/domain/money_test.dart — pure Dart, `dart test`, no flutter_test import.
import 'package:test/test.dart';
import 'package:app_core/app_core.dart';

void main() {
  // Round-trip: encoding is lossless across the ISO-4217 exponent range.
  test('minor-units round-trip for exponents 0, 2, 3', () {
    for (final currency in [Currency.jpy, Currency.usd, Currency.kwd]) {
      final money = Money(amountMinor: 12345, currency: currency);
      expect(Money.parse(money.format(), currency), equals(money));
    }
  });
}

Inject the clock; pin it in tests

注入 clock;在测试中固定时间

Production reads
clock.now()
; tests freeze it so time-dependent logic is deterministic — no wall clock, no flake.
dart
// Production: a Reminder is due when its target time has passed.
bool isDue(Reminder r) => !clock.now().isBefore(r.dueAt);

// test/domain/reminder_test.dart
test('reminder is due exactly at its due instant', () {
  final due = DateTime.utc(2026, 1, 1, 9);
  withClock(Clock.fixed(due), () {
    expect(isDue(Reminder(dueAt: due)), isTrue);
  });
});
For code behind a provider, inject
clockProvider
and freeze it in a container test with
clockProvider.overrideWithValue(Clock.fixed(due))
— same
Clock
, same fixed instant, driven through Riverpod instead of the ambient zone.
生产代码读取
clock.now()
;测试将其冻结,使依赖时间的逻辑保持确定性——不依赖墙上时钟,也不会出现 flaky。
dart
// Production: a Reminder is due when its target time has passed.
bool isDue(Reminder r) => !clock.now().isBefore(r.dueAt);

// test/domain/reminder_test.dart
test('reminder is due exactly at its due instant', () {
  final due = DateTime.utc(2026, 1, 1, 9);
  withClock(Clock.fixed(due), () {
    expect(isDue(Reminder(dueAt: due)), isTrue);
  });
});
对于位于 provider 之后的代码,应注入
clockProvider
,并在 container test 中通过
clockProvider.overrideWithValue(Clock.fixed(due))
冻结它——使用同一个
Clock
、同一个固定时刻,并通过 Riverpod 而非环境 zone 驱动。

Seeded fuzz against an independent oracle

使用带种子的 fuzz 对照独立 oracle

Twenty lines of
package:test
give you what a stale property package would, with the generated input as the built-in minimal repro. The oracle must be independent of the function under test.
dart
// test/domain/allocate_test.dart
test('allocate: parts always sum to the input (fuzz)', () {
  final rng = Random(0xC0FFEE);
  for (var seed = 0; seed < 500; seed++) {
    final n = rng.nextInt(20) + 1;
    final total = rng.nextInt(100000);
    final weights = List.generate(n, (_) => rng.nextInt(500));
    final parts = allocate(amountMinor: total, weights: weights);
    expect(parts.fold(0, (a, b) => a + b), equals(total),
        reason: 'seed=$seed total=$total weights=$weights'); // its own repro
  }
});
仅需二十行
package:test
就能替代陈旧的 property package,并让生成的输入天然成为最小复现。oracle 必须独立于被测函数。
dart
// test/domain/allocate_test.dart
test('allocate: parts always sum to the input (fuzz)', () {
  final rng = Random(0xC0FFEE);
  for (var seed = 0; seed < 500; seed++) {
    final n = rng.nextInt(20) + 1;
    final total = rng.nextInt(100000);
    final weights = List.generate(n, (_) => rng.nextInt(500));
    final parts = allocate(amountMinor: total, weights: weights);
    expect(parts.fold(0, (a, b) => a + b), equals(total),
        reason: 'seed=$seed total=$total weights=$weights'); // its own repro
  }
});

Real in-memory Drift, never a mocked DAO

使用真实的内存 Drift,绝不 mock DAO

dart
// test/data/order_dao_test.dart
void main() {
  late AppDatabase db;
  setUp(() => db = AppDatabase(NativeDatabase.memory()));
  tearDown(() => db.close()); // reactive .watch() streams must not leak timers

  test('CHECK constraint rejects a negative quantity', () {
    expect(
      () => db.orderDao.insertItem(const OrderItem(quantity: -1)),
      throwsA(isA<SqliteException>()), // proves the real constraint, not a stub
    );
  });
}
dart
// test/data/order_dao_test.dart
void main() {
  late AppDatabase db;
  setUp(() => db = AppDatabase(NativeDatabase.memory()));
  tearDown(() => db.close()); // reactive .watch() streams must not leak timers

  test('CHECK constraint rejects a negative quantity', () {
    expect(
      () => db.orderDao.insertItem(const OrderItem(quantity: -1)),
      throwsA(isA<SqliteException>()), // proves the real constraint, not a stub
    );
  });
}

Headless Notifier tests with
ProviderContainer

使用
ProviderContainer
无头测试 Notifier

dart
// test/features/cart/cart_notifier_test.dart
void main() {
  test('addItem publishes the new total', () async {
    final container = ProviderContainer(
      overrides: [orderRepositoryProvider.overrideWith((ref) => _FakeOrderRepo())],
    );
    addTearDown(container.dispose);

    await container.read(cartNotifierProvider.future);
    await container.read(cartNotifierProvider.notifier).addItem(_anItem);

    final state = container.read(cartNotifierProvider);
    expect(state, isA<AsyncData<CartState>>());
    expect(state.requireValue.totalMinor, 1299);
  });
}
ProviderContainer.test()
(Riverpod 3.x) auto-disposes and is the preferred form when available; otherwise
addTearDown(container.dispose)
.
dart
// test/features/cart/cart_notifier_test.dart
void main() {
  test('addItem publishes the new total', () async {
    final container = ProviderContainer(
      overrides: [orderRepositoryProvider.overrideWith((ref) => _FakeOrderRepo())],
    );
    addTearDown(container.dispose);

    await container.read(cartNotifierProvider.future);
    await container.read(cartNotifierProvider.notifier).addItem(_anItem);

    final state = container.read(cartNotifierProvider);
    expect(state, isA<AsyncData<CartState>>());
    expect(state.requireValue.totalMinor, 1299);
  });
}
ProviderContainer.test()
(Riverpod 3.x)会自动 dispose,并且是可用时更推荐的形式;否则使用
addTearDown(container.dispose)
。

Drive timers with
fakeAsync

使用
fakeAsync
驱动定时器

dart
test('debounced search fires once after the window', () {
  fakeAsync((async) {
    final calls = <String>[];
    final debouncer = Debouncer(const Duration(milliseconds: 300));
    debouncer.run(() => calls.add('a'));
    async.elapse(const Duration(milliseconds: 299));
    expect(calls, isEmpty);
    async.elapse(const Duration(milliseconds: 1));
    expect(calls, ['a']);
  });
});
dart
test('debounced search fires once after the window', () {
  fakeAsync((async) {
    final calls = <String>[];
    final debouncer = Debouncer(const Duration(milliseconds: 300));
    debouncer.run(() => calls.add('a'));
    async.elapse(const Duration(milliseconds: 299));
    expect(calls, isEmpty);
    async.elapse(const Duration(milliseconds: 1));
    expect(calls, ['a']);
  });
});

Anti-patterns

反模式

  • Inverting the pyramid — driving pure
    f(input) -> output
    logic through
    pumpWidget
    . Slower, flakier, hides the broken layer.
  • DateTime.now()
    / ambient
    Random()
    reachable from domain logic.
    Makes "identical inputs -> identical output" untestable; inject
    Clock
    and a seed.
  • flutter_test
    imported into a pure package.
    Breaks the structural boundary and slows the fastest tier; the package should declare no Flutter SDK constraint.
  • A mocked DAO standing in for the database. Proves nothing about SQL, constraints, indexes, or migrations. Use
    NativeDatabase.memory()
    .
  • ==
    on doubles
    , or covering a universal claim with one lucky example instead of a fuzz loop over an independent oracle.
  • A fake that lies about its contract (a dependency that can never fail) — the test then reflects a happy-path stub, not real behaviour. Fakes must expose failure.
  • Missing
    registerFallbackValue
    for a custom
    any()
    argument — a runtime throw no compiler catches.
  • pumpAndSettle()
    on an indefinite indicator
    — hangs on the 10-minute timeout.
  • A global/directory coverage-percentage gate — rewards vanity tests, and the raw number lies upward. Floor the unrecoverable files; publish the rest.
  • A green test over a structurally-untestable path (real audio, native tile, device diversity) — worse than an admitted gap, because it stops anyone checking by hand.
  • A test with no meaningful
    expect
    , shared mutable state across tests, or a vague name like
    test('works')
    .
  • 反转测试金字塔——通过
    pumpWidget
    驱动纯粹的
    f(input) -> output
    逻辑。这样更慢、更易波动,还会掩盖发生故障的层级。
  • 领域逻辑可以访问
    DateTime.now()
    或环境中的
    Random()
    。
    这会导致“相同输入产生相同输出”无法测试;应注入
    Clock
    和 seed。
  • 在纯 package 中导入
    flutter_test
    。
    这会破坏结构性边界并拖慢最快的测试层级;该 package 不应声明 Flutter SDK constraint。
  • 使用 mocked DAO 代替数据库。 这无法证明 SQL、constraints、indexes 或 migrations 的任何行为。应使用
    NativeDatabase.memory()
    。
  • 对 double 使用
    ==
    ,或者仅用一个碰巧通过的示例覆盖某项普适性断言,而不是使用 fuzz 循环和独立 oracle。
  • 违反契约的 fake(某个永远不会失败的依赖)——此时测试反映的只是一个 happy-path stub,而非真实行为。Fake 必须能够暴露失败。
  • 自定义
    any()
    参数缺少
    registerFallbackValue
    ——会触发编译器无法捕获的运行时异常。
  • 对无限持续 indicator 调用
    pumpAndSettle()
    ——会一直卡住,直到 10 分钟超时。
  • 设置全局或目录级 coverage 百分比门禁——这会奖励虚荣测试,而且原始数字会向上虚报。应仅为不可恢复文件设置下限,其余 coverage 予以发布。
  • 为结构上无法测试的路径提供绿色测试(真实音频、native tile、设备差异)——这比坦承缺口更糟糕,因为它会阻止任何人进行人工检查。
  • 测试中没有有意义的
    expect
    、测试之间共享可变状态,或者使用
    test('works')
    这类含糊名称。

Definition of done

完成标准

  • Each test lives at the cheapest tier that can assert it; pure logic under
    package:test
    , widget/integration only where the UI/plugin seam is the point.
  • Domain logic reads
    clock.now()
    , never
    DateTime.now()
    ; tests pin it with
    withClock
    .
  • Every conversion has a round-trip test and boundary rounding goldens; every universal claim is a seeded fuzz/property test against an independent oracle, with the input echoed in
    reason:
    .
  • The data layer is tested against
    NativeDatabase.memory()
    , not a mocked DAO;
    db.close()
    in teardown.
  • Owned collaborators are bare-
    implements
    fakes;
    mocktail
    is reserved for external deps with
    registerFallbackValue
    for every custom
    any()
    type.
  • Notifiers are tested through
    ProviderContainer
    overrides, disposed in teardown; no widget pumped to test state.
  • One acceptance gate asserts a realistic scenario exactly and the conservation invariant; the primitive carries a runtime
    assert
    tripwire.
  • Coverage floors sit on the unrecoverable-bug files; the coverage-lies-upward gap is fixed (untested files included); generated files stripped.
  • No
    pumpAndSettle()
    on an indefinite animation; timers driven by
    fakeAsync
    .
  • The suite stays within its time budget; every structurally-untestable path is named in the manual pre-release pass, not faked green.
  • 每个测试都位于能够断言其行为且成本最低的层级;纯逻辑使用
    package:test
    ,只有在 UI/plugin seam 本身是测试目标时才使用 widget/integration。
  • 领域逻辑读取
    clock.now()
    ,绝不读取
    DateTime.now()
    ;测试使用
    withClock
    固定时间。
  • 每种转换都有 round-trip 测试和边界 rounding golden;每项普适性断言都有带种子的 fuzz/property test,并对照独立 oracle,同时在
    reason:
    中回显输入。
  • 数据层针对
    NativeDatabase.memory()
    测试,而不是 mocked DAO;在 teardown 中调用
    db.close()
    。
  • 自己拥有的协作者使用 bare-
    implements
    fake;
    mocktail
    仅用于外部 deps,并为每种自定义
    any()
    类型设置
    registerFallbackValue
    。
  • Notifiers 通过
    ProviderContainer
    overrides 测试,并在 teardown 中 dispose;不通过 pump widget 测试状态。
  • 一个 acceptance gate 精确断言现实场景和守恒不变量;基础原语包含运行时
    assert
    绊线。
  • Coverage 下限设置在不可恢复 bug 的文件上;修复
    coverage-lies-upward
    缺口(纳入未测试文件);排除生成文件。
  • 不对无限持续动画调用
    pumpAndSettle()
    ;使用
    fakeAsync
    驱动定时器。
  • 测试套件保持在时间预算内;每条结构上无法测试的路径都明确列入 manual pre-release pass,而不是伪造为通过。

Related skills

相关 skills

  • See
    widget-golden-and-a11y-testing
    for golden lanes, the overflow/RTL matrix, and honest a11y limits.
  • See
    value-objects-money-and-units
    for the
    allocate()
    primitive and
    Clock
    injection this skill tests.
  • See
    persistence-drift
    for the DAO/value-object seam the in-memory tests exercise.
  • See
    run-migration
    for the forward-only migration ritual whose from→to paths get the 100% floor.
  • See
    error-handling-typed-results
    for the sealed
    Result
    /
    Failure
    the exhaustive-switch tests assert on.
  • See
    async-safety
    for the dropped-Future holes the silence-review checklist targets.
  • See
    service-boundary-and-native
    for the injectable ports the fakes stand in for.
  • See
    ci-pipeline-and-gates
    for how these lanes and greps are wired as gates.
  • See
    seeded-determinism-and-golden-vectors
    for pinning a seeded generator with a frozen fingerprint table when output must reproduce across devices and releases.
  • 参见
    widget-golden-and-a11y-testing
    ,了解 golden lanes、overflow/RTL matrix,以及真实的 a11y 限制。
  • 参见
    value-objects-money-and-units
    ,了解本 skill 所测试的
    allocate()
    基础原语和
    Clock
    注入。
  • 参见
    persistence-drift
    ,了解内存测试所覆盖的 DAO/value-object seam。
  • 参见
    run-migration
    ,了解 forward-only migration 流程及其获得 100% 下限的 from→to paths。
  • 参见
    error-handling-typed-results
    ,了解 exhaustive-switch 测试所断言的 sealed
    Result
    /
    Failure
    。
  • 参见
    async-safety
    ,了解 silence-review checklist 所针对的 dropped-Future 漏洞。
  • 参见
    service-boundary-and-native
    ,了解 fake 所替代的 injectable ports。
  • 参见
    ci-pipeline-and-gates
    ,了解这些 lanes 和 greps 如何被接入 gates。
  • 参见
    seeded-determinism-and-golden-vectors
    ,了解如何在输出必须跨设备和 release 可复现时,使用冻结的 fingerprint table 固定带种子的 generator。

References

参考资料