Aller au contenu principal

Org-Wide Testing Strategy

Principle​

One standard, applied consistently across all repositories. The depth scales with the repo's risk, not the language. Every repo must pass the same CI gate structure before anything reaches staging or main.

Tests are not optional β€” they are a first-class deliverable on the same level as the feature code.


The 5 Layers​

LayerWhat it checksSpeedWhen it runs
L0 β€” StaticLint, format, types, YAML schema< 1 minEvery push, every branch
L1 β€” UnitPure logic, no external deps, mocks everything< 5 minEvery push
L2 β€” IntegrationReal DB, real queue, mocked HTTP< 15 minPR to staging
L3 β€” ContractAPI shape matches what consumers expect< 5 minPR to staging
L4 β€” E2E / SmokeFull happy path on real infra< 10 minPR to main

CI Gate Mapping​

This maps directly onto the existing branch strategy (dev β†’ staging β†’ main).

dev push β†’ L0 + L1 (~5 min, fast feedback)
PR β†’ staging β†’ L0 + L1 + L2 + L3 (~20 min, blocking)
PR β†’ main β†’ L0 + L1 + L2 + L3 + L4 (full suite + SBOM/cosign)

Fail-fast rule: L0 always runs before L1, L1 before L2. No point spinning up a database if linting fails.


Language Matrix​

StackL0L1L2L3L4
Python (Frappe)ruff + mypypytestbench run-tests + httpxschemathesiskubectl exec + httpx
Python (scripts)ruff + mypypytestpytest + testcontainersβ€”manual
Gogolangci-lintgo test ./...go test + testcontainersopenapi-validatorhttpx / curl
TypeScripteslint + tscvitestvitest + MSWβ€”Playwright
YAML / Helmyamllint + kubeconformhelm linthelm template + kube-scoreβ€”ArgoCD diff
HCL (OpenTofu)tofu fmt + validateβ€”tofu plan (dry-run)β€”manual
Ansibleansible-lintmolecule testmolecule convergeβ€”manual

Mandatory Conventions (every repo)​

Directory layout​

tests/
unit/ # L1 β€” pure logic, no external deps
integration/ # L2 β€” real DB / queue, Docker Compose or testcontainers
e2e/ # L4 β€” smoke tests against real cluster
fixtures/ # shared test data, factory functions

Rules​

  1. make test runs L1 locally β€” no Docker, no network, <5 min
  2. make test-integration runs L2 with Docker Compose
  3. Coverage threshold: 70% on business logic files (excludes config, glue, __init__.py)
  4. Every new public function or API endpoint β†’ at least one happy-path test + one failure case
  5. No # noqa / // nolint without an inline comment explaining the exception
  6. Test file names mirror the module they test: dsn_generator.py β†’ test_dsn_generator.py
  7. Fixtures live in tests/fixtures/ β€” never inline large data blobs in test functions

Repo Classification​

Not all repos carry the same risk. Required test layers scale accordingly:

TierReposRequired layers
A β€” Business logicminicloud-erpnext, minicloud-plane, platform-demoL0 β†’ L4 (full)
B β€” Infrastructure codeminicloud-gitops, minicloud-opentofu, minicloud-ansibleL0, L2 (plan/dry-run), L4 (ArgoCD diff)
C β€” UI / docsminicloud-backstage, ktayl-solution-web, minicloud-platform-docsL0, L1, L4 (Playwright)
D β€” Tooling / opsminicloud-ops, minicloud-open-webui, minicloud-onlyofficeL0, L1

Rollout Plan​

WeekRepoTierFirst deliverable
1minicloud-erpnextAL0 + L1: ruff/mypy + pytest for DSN generator, CRM parser, Factur-X
2platform-demoAL0 + L1: golangci-lint + go test (already has CI structure)
3minicloud-planeAL0 + L1: golangci-lint + go test for webhook/NATS logic
4minicloud-gitopsBL0: yamllint + kubeconform + helm lint on every PR
5+remaining reposC/DL0 + L1 in parallel

Reference: minicloud-erpnext​

The first implementation. Serves as the template for all Tier A Python repos.

Test file map​

tests/
conftest.py # mock frappe module for unit tests (no bench needed)
fixtures/
employees.py # standard + edge-case employee dicts
crm_responses.py # ACCEPTE, REJETE, SOAP fault, empty β€” as strings
unit/
test_dsn_generator.py # build_dsn(): CRLF, UTF-8, S10/S20/S90 blocks
test_dsn_submitter.py # _response_is_ok(): 8 CRM XML scenarios
test_api_helpers.py # _contract_type_code(), _collect_warnings()
test_facturx.py # _build_cii_xml(): CII XML structure assertions
integration/ # L2 β€” bench run-tests (future)
e2e/ # L4 β€” kubectl exec smoke test (future)

CI jobs​

# .github/workflows/test.yml
jobs:
lint: # ruff check + ruff format --check + mypy (every push)
test-unit: # pytest tests/unit/ --cov --cov-fail-under=70 (every push, needs: lint)

Running locally​

# Install test deps (once)
pip install -r requirements-test.txt

# L0 β€” lint + type check
make lint

# L1 β€” unit tests with coverage
make test-cov

# Auto-fix formatting
make fmt

Why This Approach​

No Frappe in unit tests. The bench/frappe runtime is only available inside the ERPNext Docker image. Unit tests mock the frappe module via sys.modules so they run in any Python 3.11 environment β€” CI, local, GitHub Actions β€” without a running site.

Coverage on business logic only. Frappe hooks, __init__.py files, and setup scripts are excluded. The 70% threshold applies to the files that actually contain business logic (dsn_generator.py, dsn_submitter.py, facturx.py, etc.).

Tests document behaviour. Test names use the form test_WHAT_CONDITION (e.g. test_response_is_ok_rejete_returns_false) so the test suite doubles as executable specification of what each function must do.