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

Testing Standard

Testing is a landing requirement, not a follow-up. No crate is considered done until it meets this standard, and CI enforces the mechanical parts.

Per-crate requirements

Every crate carries:

  • Unit tests for each public function and the non-obvious private ones, under the workspace 85% per-file coverage ratchet (coverage may only rise).
  • Property tests (proptest) wherever there is a round-trip, an invariant, or a parser/encoder. Serialization, decode/encode, cursor arithmetic, type-mapping compatibility, and DAG locator propagation all qualify.
  • Snapshot tests (insta) for config parsing and any generated artefact (DDL, schema translation, contract resolution output).
  • Doc tests on public API examples, so the docs cannot drift from behaviour.

Sans-io crates (twg-codec-*, twg-proto-flatten, twg-proto-schema, twg-proto-decode, twg-wire-sasl, twg-type-map, twg-contract-core) must test entirely without a runtime or containers — that testability is the point of the sans-io discipline, and a container dependency creeping into their tests is a layering smell.

End-to-end requirements

The twg-e2e crate holds cross-crate scenarios and regression tests:

  • Scenarios (tests/scenarios.rs) exercise realistic full-path flows through Testcontainers (Kafka, Pulsar, Postgres, Unity Catalog OSS). They are skipped gracefully when Docker is unavailable, so the workspace still builds and unit tests still run without it.
  • Regressions (tests/regressions.rs) encode known-hard cases as permanent assertions. Each entry corresponds to a specific failure mode; once written it never leaves.

The Zerobus sink is the one integration with no open-source double, so its contract is tested against a hand-rolled fake implementing the sink trait. The fake enforces the behaviours learned in production — mid-stream schema changes rejected, the SDK breaking-surface pinned — so the contract is verified even though the real service is not in CI. A thin smoke suite gated on real credentials runs outside PR CI.

Benchmarks as gates, not curiosities

Where a decision rests on performance, the benchmark is part of the decision and runs in CI as a regression gate — not a one-off measurement quoted in a document.

The protobuf decode strategy is the current case. A zero-copy descriptor-driven parser is to be vendored and extended rather than depended upon (see twg-codec-protobuf), and that divergence is only justified if it pays: the upstream parser is the baseline to beat, measured on our hardware and our schemas, with prost-reflect as the reference floor. Include a wide schema (100+ fields) — that is where reflection-based decoders collapse and where the gain is largest, so it is the case that decides the question. If the fused-into-ArrayBuilders path does not beat parse-then-walk by a real margin, the fusion is not worth maintaining.

Criterion, committed baselines, and a CI failure on regression beyond a stated tolerance. See BENCHMARKING.md for how to run it and, more usefully, for the cheaper premise check to do before implementing anything.

The regression list is a debt ledger

Every item in tests/regressions.rs is currently a comment. Turning each into an executable, passing test is tracked work — a crate that touches the relevant area may not land while its regression is still a comment. See SCENARIOS.md for the authoritative scenario and regression catalogue with rationale.