Testing & Quality
Testing isn't optional — it's how you prove you care.
Quality here isn't a checklist. It's a design force — every layer of the stack has a gate, every gate has teeth, and nothing reaches production without proving it belongs there.
First Principles
- A gate is binary — pass or fail, never "mostly acceptable"
- Gates have teeth — a warning is not a gate; exit code 1 is
- Gates belong in the feedback loop — pre-push or CI, not PR review
- Coverage is a floor, not a ceiling — mutation score proves the tests mean something
- Property tests find what unit tests assumed away
- Measure at natural granularity — per-method, per-type, per-namespace; never global averages that mask local debt
- Self-hosted — QualityGate gates itself: 256 tests, 100% line/branch coverage, mutation score 1.0
Test-Driven Design
- TDD — tests drive design, not just verify it
- No mocking frameworks —
FakeHttpClient, hand-written fakes, deterministic tests - Mutation testing (Stryker) — test quality score >= 0.95
- 100% code coverage as design pressure, not a vanity metric
- Property-based testing (fast-check) for fuzzy invariants
Professional Testing at Scale
- Revived ~800 unit tests at BIM&CO — built a multi-threaded execution framework with async lambda encapsulation, cutting test suite from non-functional to running in ~20 minutes
- Aspire testing layer — mini framework on top of .NET Aspire to reduce test boilerplate (BIM&CO)
- CucumberJS E2E — set up the npm/TypeScript + CucumberJS test stack with Selenium infrastructure (Qwant)
- Selenium — E2E testing coordination across multiple projects (Qwant, AUSY)
Code Quality as Static Analysis
Not opinions — metrics. QualityGate runs Roslyn-powered analysis on every build: cyclomatic complexity, cognitive complexity, afferent/efferent coupling (Ca/Ce), cohesion (LCOM4), maintainability index. Thresholds live in quality-gate.yml, enforced in CI with pass/fail exit codes. The build doesn't warn — it fails.
Visual Quality as Baselines
Every page is screenshotted across 4 themes (dark, light, dark-hc, light-hc) and multiple viewports. Baselines are checked in. When a pixel changes, the diff is visible and intentional — or the test fails.
New pages get auto-generated baselines on first run. A smoke mode tests a curated set of stable pages on every push, so content additions don't break the suite. Full regression runs on demand.
Accessibility as a Gate, Not an Afterthought
axe audits on every page, accent color contrast matrix testing, ARIA role verification, keyboard-first navigation, screen-reader announcements. Accessibility isn't a separate pass — it's woven into the same test pipeline that checks rendering.
Requirements as Compile-Time Proof
Features are abstract classes with acceptance criteria as abstract methods. Tests link to features via typed decorators. A compliance scanner verifies 100% AC coverage — if an acceptance criterion has no test, the build knows.
The Gate Before Push
Husky pre-push hook: unit tests + smoke Playwright + compliance scan. All three must pass before git push succeeds. No bypassing, no --no-verify. If the gate fails, the code stays local until it's fixed.
C# Quality Gates
| Gate | Tool | Metric | Threshold |
|---|---|---|---|
| Complexity | QualityGate (Roslyn) | Cyclomatic / cognitive per method | ≤ 15 / ≤ 20 |
| Cohesion | QualityGate (Roslyn) | LCOM4 per type | ≤ 25 |
| Coupling | QualityGate (Roslyn) | Efferent coupling per type | ≤ 70 |
| Maintainability | QualityGate (Roslyn) | Maintainability index per method | ≥ 55 |
| Architecture | QualityGate (Roslyn) | Distance from main sequence per namespace | ≤ 1.0 |
| Coverage | XPlat Code Coverage (coverlet) | Line + branch | 100% |
| Mutation | Stryker.NET | Mutation score | ≥ 0.95 |
| Property testing | CsCheck | Randomized invariants | 0 counterexamples |
| CI gate | quality-gate check |
All gates above | exit 0 |
TypeScript Quality Gates
| Gate | Tool | Metric | Threshold |
|---|---|---|---|
| Type safety | TypeScript strict | Type errors | 0 |
| Lint | ESLint | Violations | 0 |
| Coverage | Istanbul / nyc | Line + branch | 100% |
| Mutation | Stryker (stryker-mutator) | Mutation score | ≥ 0.90 |
| Property testing | fast-check | Randomized invariants | 0 counterexamples |
| Visual regression | Playwright | Pixel diff vs baseline | 0 |
| Accessibility | axe-core | Violations | 0 |
| Pre-push gate | Husky | All gates above | blocked on failure |