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 and Release Policy

SCHEMABOUND treats testing and release discipline as part of the public contract. The goal is not just to ship code that works, but to ship public surfaces that are validated, predictable, and safe to adopt.

Test Layers

Each layer has a fixed contract. Violating that contract — mocking in integration tests, introducing I/O in unit tests, stubbing a container away in an E2E test — is a test smell, not a trade-off.

LayerCollaboratorsMocks?Stubs?Where it runs
UnitIn-process onlyNeverStubs are acceptableLocal, no I/O
IntegrationReal containerized runtimesNeverNeverLocal containers (Testcontainers or Compose)
End-to-EndFully deployed environmentNeverNeverLocal or staging deployment

Unit Tests

Unit tests should stay fast, deterministic, and in-process.

Expectations:

  • no live RPC or HTTP calls
  • no containers
  • no shared ports or database paths between test cases

Stubs and mocks are not the same thing. A stub returns a fixed value to put the system under test into a known state — that is acceptable. A mock asserts that the system under test calls it in a specific way (call count, argument shapes). If a unit test needs a mock, the production dependency has a design problem. Simplify the dependency before adding mock scaffolding.

If a unit test needs a large stub or mock hierarchy, treat that as a signal to simplify the production code, not to add more scaffolding.

Test isolation. Each test that starts a server or opens a database must use a unique address and path. Use atomic counters or unique temporary paths to avoid flaky collisions:

  • Unique ports per test — allocate with an atomic counter, never hardcode a shared port.
  • Unique database paths — use a helper like get_test_db_path() that returns a temp-scoped unique path per test.

Assertion style. Prefer exact value assertions over substring matching.

  • Use matches!() for enum variant checks instead of .to_string().contains().
  • Avoid snapshot tests — write explicit expected values so failures are immediately actionable.
  • Assert on typed values, not serialized strings, wherever the types are available.

Integration Tests

Integration tests should validate real boundaries.

Expectations:

  • real network requests over the actual protocol (TCP, gRPC, HTTP)
  • a runtime that actually starts and listens on a real port
  • real dependencies where the boundary matters — no shortcut local clients standing in for protocol behavior
  • no stubs or mocks at any layer

If a test is named integration, it must talk to a real started runtime over the network. Tests that call in-process implementations directly are unit tests, not integration tests, regardless of what they are named.

End-To-End Tests

End-to-end tests validate a deployed environment from the outside. They are the right fit for rollout, wiring, secret, and networking concerns when that environment exists.

Expectations:

  • fully deployed environment, nothing replaced or stubbed
  • tests drive the system only through public entry points (API, CLI, browser)
  • environment lifecycle (start, seed, teardown) is explicit and automated

CI Expectations

Pull requests to main should pass the quality profile relevant to the repository being changed. That can include:

  • linting and formatting
  • unit and integration tests
  • build validation
  • documentation builds
  • coverage or maintainability gates where they add real signal
  • dependency and security checks

SDK-specific maintainability gates are intentionally selective. They should exist where a codebase contains enough handwritten logic to justify them, not just because a language binding exists.

Local Validation

When available, enable the repo-managed hooks locally:

make hooks-install

The local hook path is intended to catch common failures before you open a pull request. It complements CI, but does not replace it.

Release Discipline

Validation and publication are separate steps.

The expected release path is:

  1. merge reviewed code to main
  2. allow validation to complete on the merged revision
  3. create the appropriate release tag when publication is intended

Current public release patterns include:

  • public-v* for public subtree publication
  • sdk-python-v* for Python SDK publication
  • sdk-<language>-v* as the general form for future SDK release workflows

Why This Separation Exists

Separating merge validation from publication keeps the public SCHEMABOUND surface more predictable for adopters.

It ensures that:

  • every published artifact passed review first
  • release timing is deliberate rather than accidental
  • public packages and docs stay aligned with validated source
  • teams can reason about adoption risk more clearly