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.
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>orgiven_<state>__when_<action>__then_<outcome>. - All assertions use AssertJ. Do not introduce JUnit 4 assertions.
- Mocks use Mockito 5 (including
mockStaticfor static helpers). PowerMock is forbidden. - Fixture resources go under
src/test/resources/fixtures/and use the<feature>-<scenario>.jsonnaming 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.Clockinstead of callingLocalDateTime.now(). The sharedFixedClockConfigtest 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 atry-with-resourcesblock. - WebFlux paths are exercised with
WebTestClient;MockMvcis not used in this repository. - gRPC is exercised with
grpc-testingandInProcessChannelBuilder; no real sockets in tests. - Async waits go through Awaitility (
org.awaitility.Awaitility.await()) with a polling interval.Thread.sleepin 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 indc3-common-test. - Sensitive data (api keys, tokens, passwords) is replaced with the
literal string
should-not-leakin 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 assertgetIndex()round-trips throughofIndex(index)for every constant.SecretFieldContractTest: subclass it with the VO classes you want to validate; the suite asserts secret-bearing fields never leak throughtoString().
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
unitandintegrationjobs must be green before merge. - The
coveragejob uploads the aggregate jacoco report as a workflow artifact namedjacoco-aggregateand 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.