Files
iot-dc3/docs/development/testing.md
T
pnoker bca6d7aedc docs(site): scaffold MkDocs Material documentation site with GitHub Pages CI
Set up an end-to-end documentation site so contributors and users have
a unified, searchable, and publishable entry point for project docs.

- Add mkdocs.yml: Chinese-first language, light/dark palette, tabbed
  navigation, indexed sections, glightbox for diagrams, and
  pymdownx.snippets so community pages can pull from root governance
  files (CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md) without
  duplicating content.
- Add requirements-docs.txt pinning mkdocs-material and
  mkdocs-glightbox.
- Build the docs/ skeleton: index, quickstart, guide, architecture,
  development, modules, community. Migrate the seven generic manuals
  from dc3/doc/ (QUICKSTART, ENVIRONMENT, LOGGING, TROUBLESHOOTING,
  MODULES, DRIVER-AUTHORING, TESTING) and rewire their cross-doc
  links. CHANGE.md, TITLE.md, and USAGE.md stay in dc3/doc/ because
  docker-ci.yml depends on them; the docs site references them via
  mkdocs snippets to keep a single source of truth.
- Add .github/workflows/docs.yml that builds with --strict on push to
  release and deploys via actions/deploy-pages.
- Rewrite dc3/doc/README.md as a migration pointer to the new site
  and to the three retained release-flow files.

Verification: mkdocs build --strict passes locally; snippets render
correctly; GitHub Pages already enabled in workflow mode via gh api.
2026-05-18 18:36:00 +08:00

8.8 KiB

IoT DC3 Testing Guide

This document is the canonical testing reference for the iot-dc3 backend repository. It complements AGENTS.md (engineering rules), LOGGING.md (runtime logging conventions) and ENVIRONMENT.md (runtime configuration).

1. Test Pyramid

Layer Goal Mechanics Target share
Unit Fast, isolated business logic checks JUnit 5 + Mockito + AssertJ ~70%
Slice Spring slice tests for controllers, JSON, persistence @WebFluxTest, @JsonTest, @MybatisPlusTest ~25%
Integration Real infrastructure via Testcontainers, gRPC in-process PG18+TimescaleDB, RabbitMQ, MQTT, gRPC InProcess included in slice/integration share
End-to-end Infrastructure-backed harness now; full user flows next dc3-e2e with Testcontainers + rest-assured ~5%

Aggregate coverage gates (read from dc3-coverage):

  • Repository line coverage ≥ 80%
  • Critical-domain line coverage ≥ 90% (auth, manager, data, driver SDK, public utilities, vendored modbus codecs)
  • Repository branch coverage ≥ 65%

Coverage is intentionally relaxed in the early stages and tightened as each test stage lands. The current threshold is configured in dc3-coverage/pom.xml.

2. Directory & Naming Conventions

Each module that owns test code follows the same layout:

<module>/src/test/
├── java/<package>/
│   ├── unit/           # *Test.java; Surefire executes
│   ├── slice/          # *SliceTest.java; @WebFluxTest / @JsonTest etc.
│   ├── integration/    # *IT.java; Failsafe executes; @Tag("integration")
│   ├── contract/       # *ContractTest.java; extends a dc3-common-test base
│   └── support/        # Module-specific fixtures
└── resources/
    ├── application-test.yml
    ├── fixtures/       # JSON / CSV / protocol byte streams
    └── logback-test.xml

Naming rules:

  • Unit tests: *Test.java. Surefire matches by default.
  • Integration tests: *IT.java. Failsafe matches by default.
  • Test method names: should_<expected>_when_<condition> or given_<state>__when_<action>__then_<outcome>.
  • All assertions use AssertJ. Do not introduce JUnit 4 assertions.
  • Mocks use Mockito 5 (including mockStatic for static helpers). PowerMock is forbidden.
  • Fixture resources go under src/test/resources/fixtures/ and use the <feature>-<scenario>.json naming pattern.

3. When Tests Are Required

Change type Required action
Bug fix Add a regression test that fails before the fix
New feature / behaviour change Add unit + slice/integration coverage
Refactor Preserve coverage; add a contract test if behaviour was previously implicit
DAL / SQL change Run make test-it; add a Testcontainers slice test
gRPC proto change Update server + stub tests in the same PR
Pure docs / formatting No test required

The Validation Checklist in AGENTS.md enumerates the full matrix.

4. Time, Randomness, IO

  • Inject a java.time.Clock instead of calling LocalDateTime.now(). The shared FixedClockConfig test configuration produces a deterministic instant; import it via @Import(FixedClockConfig.class).
  • Inject a RandomGenerator (or seed it) — never rely on real entropy.
  • Static utilities are mocked with Mockito.mockStatic(...) inside a try-with-resources block.
  • WebFlux paths are exercised with WebTestClient; MockMvc is not used in this repository.
  • gRPC is exercised with grpc-testing and InProcessChannelBuilder; no real sockets in tests.
  • Async waits go through Awaitility (org.awaitility.Awaitility.await()) with a polling interval. Thread.sleep in tests is forbidden.

5. Testcontainers Conventions

Containers expose a single shared instance per JVM, started lazily on class-load. Cross-run reuse is opt-in and only enabled when the local Testcontainers environment supports it. Pin images to the production-aligned tag:

Container Image Notes
Postgres + TimescaleDB timescale/timescaledb-ha:pg18 Mirrors the production Dockerfile under dc3-docker/dc3/dependencies/postgres
RabbitMQ rabbitmq:3.13-management Publisher confirms + mandatory delivery enabled, mirroring RabbitConfig
MQTT eclipse-mosquitto:2.0 Anonymous access on port 1883 for driver tests

The shared wrappers live in dc3-common/dc3-common-test/src/main/java/io/github/pnoker/test/containers/ and inject themselves into Spring environments via @DynamicPropertySource callbacks.

For local reuse, contributors can opt in by creating ~/.testcontainers.properties:

testcontainers.reuse.enable=true

6. Test Data Strategy

  • Small fixed payloads belong in src/test/resources/fixtures/ so they can be diffed in PR review.
  • Exhaustive object generation uses Instancio. Seeds are pinned with Settings.create().set(Keys.SEED, ...) for reproducibility.
  • Database state is bootstrapped per test class with Testcontainers init scripts placed under each module's src/test/resources/db/. The repository-wide TimescaleDB extension is declared once in dc3-common-test.
  • Sensitive data (api keys, tokens, passwords) is replaced with the literal string should-not-leak in fixture VOs to make accidental exposure obvious.

7. Contract Test Suites

dc3-common-test ships reusable JUnit 5 base classes:

  • EnumContractTest<E>: subclass it with the enum class to assert getIndex() round-trips through ofIndex(index) for every constant.
  • SecretFieldContractTest: subclass it with the VO classes you want to validate; the suite asserts secret-bearing fields never leak through toString().

Additional base classes (BuilderRoundTrip, TenantPropagation, ...) are introduced in their owning modules' src/test/ trees during later stages so they can refer to business BO/VO types without reverse dependencies.

8. Local Commands

make test                 # Unit phase
make test-it              # Integration phase (requires Docker)
make coverage             # Aggregate jacoco report (target/site/jacoco-aggregate)
make test-e2e             # E2E harness; exports DC3_E2E=true and requires Docker
mvn -B -pl <module> test  # Targeted unit run for a single module

All commands respect -Dskip.unit.tests=true and -Dskip.integration.tests=true if a test phase needs to be bypassed temporarily.

9. CI

Workflow Triggers Jobs
ci.yml push + PR to develop/release/main compile (fast-fail)
test.yml push + PR to develop/release/main unit, integration, coverage
e2e.yml push to develop/release/main + workflow_dispatch e2e

PR review expectations:

  • The unit and integration jobs must be green before merge.
  • The coverage job uploads the aggregate jacoco report as a workflow artifact named jacoco-aggregate and enforces the configured coverage thresholds.
  • Coverage regressions of more than 1% in the aggregate report block the PR.

10. AI Test PR Discipline

Aligned with the Commit Rules in AGENTS.md:

  • Tests for a stage land in a dedicated PR (e.g. PR-S2 for the auth domain), with commits split by intent within that PR.
  • AI-authored commits use the documented Claude <claude[bot]> identity via command-scoped environment variables; repository-level git config is never modified.
  • Before pushing a commit, the AI presents the proposed subject, the files included and the verification steps already executed.