Chapter 24Lesson 03~175 minutes

Continuous Integration Patterns for Build, Test, Lint, and Coverage: Configuration, Design Patterns, and Trade-Offs

Choose CI job boundaries, evidence depth, coverage gates, cache strategy and trigger scope from explicit reliability, latency and governance trade-offs.

Job graphFail-fastCoverage gateCachingBranch protection

Learning objectives

  • Choose single-job or staged-job CI from evidence, isolation, latency and cost requirements.
  • Distinguish “collect more evidence” from indiscriminate fail-fast or failure masking.
  • Define coverage as an explicit policy signal instead of treating a raw percentage as quality.
  • Design cache keys and clean-build checks so speed cannot become a hidden correctness dependency.
  • Give branch protection a stable check contract without coupling policy to every internal job.

1. Job boundaries are reliability boundaries

A single job shares one workspace, one tool installation and one failure sequence. That is fast and simple for a tiny repository. Separate jobs create fresh runner boundaries, clearer check names and independent evidence, but repeat checkout/setup work and can increase queue time. There is no universally “more DevOps” answer; choose the boundary that makes failures and ownership legible.

Design Strength Cost/risk Good fit
Single job Fast startup, easy local parity, simple YAML Later checks may not run after early failure; one opaque check unless carefully instrumented Small repos, fast deterministic suite
Staged jobs Isolation, parallelism, separate checks/evidence Repeated setup, more runners/artifacts, more graph complexity Larger suites or distinct ownership/policies
Hybrid + aggregate gate Detailed internal jobs plus one stable policy surface Requires explicit aggregation logic Teams using branch protection/rulesets with evolving internals

2. Fail-fast versus full evidence is a deliberate operating choice

Failing as soon as a compile step breaks minimizes wasted compute and gives the fastest primary signal. Running independent lint and tests in parallel collects more diagnostic information in one run. The correct choice depends on dependency relationships: tests that cannot execute without a successful build should not pretend to be independent, while lint often can run in parallel.

For matrices, strategy.fail-fast controls whether one non-tolerated cell cancels siblings. For ordinary jobs, graph structure and if conditions control what continues. Do not use always() indiscriminately; use it for evidence/aggregation paths that are safe after cancellation or failure.

3. Coverage percentage and coverage policy are different state

Coverage is produced by a specific tool against a specific test execution and source set. A 91% report is measurement. “At least 90% or fail” is policy. “Changed lines must be covered” is a different policy. None of them proves assertions are meaningful, failure paths are realistic, or integration behavior is safe.

Choice What it optimizes Trade-off
No hard threshold Low friction; report remains informative Coverage can silently decay without review discipline
Repository-wide threshold Simple objective gate Legacy low-coverage areas can block unrelated work; teams may game the number
Differential/changed-code policy Protects new work while legacy improves gradually Requires reliable diff/base semantics and often extra tooling
Multiple thresholds by component Reflects risk differences More configuration and governance overhead

4. Cache versus clean rebuild: use both hypotheses

A cache is useful when restoring it never changes the logical dependency result. Encode correctness-relevant inputs—OS, toolchain, lockfile hash—into the key. Avoid caching generated build outputs unless the cache key fully captures every input and the build system itself has robust incremental correctness.

High-value production suites often include a periodic clean path: scheduled or release-candidate CI that disables or bypasses selected caches and rebuilds from authoritative inputs. If warm CI passes but clean CI fails, the cache or undeclared developer-machine state has become part of the build.

5. Pull-request checks versus scheduled exhaustive suites

Trigger Primary goal Typical scope
pull_request Fast review feedback before merge lint, compile/build, focused unit/integration tests, practical coverage gate
push to protected/default branch Verify merged state and produce trusted post-merge evidence same required suite plus packaging/provenance if appropriate
schedule Find time-dependent, exhaustive or expensive failures broader compatibility, long-running integration, dependency freshness, clean-cache run
merge_group Validate merge-queue synthetic commit the same required checks expected by merge queue policy

If a repository uses merge queue and a GitHub Actions check is required, current GitHub guidance requires the workflow to handle the merge_group event as well. Otherwise the queue can wait for a check that never runs.

6. Stable check names are an interface

Branch protection does not understand that “lint-v2” replaced “lint-v1” because your team refactored YAML. It sees check names. Current GitHub rules documentation describes Actions checks by job name, and reusable workflow checks as caller job name plus reusable job name. Renaming a required job is therefore a governance migration, not cosmetic cleanup.

jobs:
  required:
    name: CI required   # treat this as a policy API
    if: always()
    needs: [dependencies, lint, build, tests]
    runs-on: ubuntu-24.04
    steps:
      - run: |
          # Explicitly validate every required upstream result.
          test "${{ needs.dependencies.result }}" = "success"
          test "${{ needs.lint.result }}" = "success"
          test "${{ needs.build.result }}" = "success"
          test "${{ needs.tests.result }}" = "success"

7. CI permissions should match the job’s side effects

Build/test jobs usually need repository read access, not issue/release/package/deployment writes. If an annotation can be emitted through workflow commands, do that rather than granting API write scopes simply to format a test failure. Separate publication or mutation jobs from untrusted PR checks.

Least privilege: do not grant write-all because one reporting action asks for it. First ask whether the report can be retained as an artifact or native check annotation under read-only CI.

8. Worked design decision: a 12-minute test suite with 30-second lint

Suppose lint takes 30 seconds, build takes two minutes, unit tests take four minutes and integration tests take six minutes. A single serial job takes roughly 12.5 minutes plus setup. Splitting lint and build in parallel, then running unit/integration after build, can shorten critical path but increases runner concurrency and repeated setup.

Requirement Selected pattern Evidence
Reviewers need lint and tests even if one fails Independent lint plus build/test branches separate job conclusions/artifacts
Merge policy must not track four evolving names Stable CI required aggregate one required check plus printed upstream results
Cache corruption must not determine correctness cache only downloads; installer always runs; nightly clean path cache-hit signal + clean-run comparison
Cost/concurrency must remain bounded avoid unnecessary matrix dimensions; stage expensive tests after build job graph and duration metrics

9. Rollback and migration considerations

When changing CI structure, preserve the old required check until the new check has appeared successfully on representative commits. Update rulesets/branch protection deliberately, then remove the old check. A YAML rename pushed before governance is updated can block merges even if every new job is green.

10. Lesson summary

CI architecture is a trade space among latency, evidence, isolation, cost and governance. Use job boundaries to clarify causal claims, coverage thresholds as explicit policy, caches as optional acceleration, exhaustive scheduled paths for broader hypotheses, and stable aggregate check names as a versioned interface to merge policy.

Next lesson

Continuous Integration Patterns for Build, Test, Lint, and Coverage: Diagnostics, Failure Modes, and Production Practices

Continue with the next lesson to build on the current concepts, evidence, security boundaries, and operational practices.

Knowledge check

When is a single CI job preferable to many jobs?

Why is a 90% coverage threshold not equivalent to “90% test quality”?

What does a nightly clean build detect that warm PR CI may hide?

Why can renaming a required job break merges?

A merge queue waits forever for CI even though PR CI is green. What should you inspect?

Official references and version notes

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.