Skip to content

Intermediate · CI/CD pipelines · topic 28 of 53 · 30 min

Testing, quality gates and artefacts

Explanation

A pipeline is only as good as its tests. The test pyramid says: many fast unit tests, fewer integration tests, and a few end-to-end tests, plus linting and static analysis. A quality gate blocks a build that fails tests, drops coverage or has critical vulnerabilities. What passes becomes an artefact: a versioned, unchangeable build output stored in a registry and tagged with the git SHA and a semantic version.

The test pyramid

Tests differ in speed, cost and how much they cover. The test pyramid is a simple guide for the mix: a wide base of small, fast tests and a narrow top of big, slow ones.

LevelWhat it checksSpeedHow many
UnitOne function or class on its own, with fakes for databases and APIsMilliseconds eachHundreds or thousands
IntegrationParts working together: the API with a real test database, a service calling anotherSeconds eachTens to hundreds
End-to-end (E2E)A whole user journey through the real UI, such as "sign up, add to cart, pay"Many seconds to minutes eachA handful of key journeys

Before any test runs, linting and static analysis read the code without running it. A linter (ESLint, Ruff, Checkstyle) catches style problems and simple bugs such as unused variables. Static analysis tools (SonarQube, Semgrep, CodeQL) find deeper problems, including some security bugs. They are the cheapest checks, so they run first.

Coverage: a signal, not a goal

Code coverage is the percentage of lines (or branches) that ran during the tests. Low coverage clearly shows untested code. But high coverage does not prove good tests: a test can run every line and check nothing. When a coverage number becomes a target, people write tests that chase the number. Use a sensible floor (for example, do not let coverage drop below the current level) and review what the tests actually assert.

Flaky tests

A flaky test sometimes passes and sometimes fails with no code change, often because of timing, shared test data or calls to real external services. Flaky tests teach the team to ignore red builds. Fix them quickly, or quarantine them: move them out of the blocking suite, with a ticket and an owner, so they do not block everyone while being fixed. Blindly re-running failed tests until they pass hides real bugs.

Quality gates

A quality gate is a set of rules a build must meet to continue: all tests pass, coverage at or above a threshold, zero lint errors, no critical vulnerabilities in dependencies or the image. If any rule fails, the pipeline stops and says why.

Artefacts

An artefact is the output of a build that you deploy or share: a container image, a .jar file, an npm package, a Python wheel, a zipped static site. Two rules matter. Versioned: every artefact has a unique version you can trace back to a commit. Immutable: once published, an artefact never changes; a fix means a new version.

Kind of artefactWhere it is stored
npm packagenpmjs.com, GitHub Packages, or a private registry such as Artifactory or Nexus
Java .jarMaven Central or a private Maven repository
Python wheelPyPI or a private index
Container imageDocker Hub, GitHub Container Registry, Amazon ECR, Azure Container Registry, Google Artifact Registry

Tagging images

Tag every image with the git commit SHA, so you always know exactly which code is running. For releases, add semantic version tags. Semantic versioning (semver) is MAJOR.MINOR.PATCH: bump MAJOR for breaking changes, MINOR for new features, PATCH for fixes. Teams often also push moving tags such as 1.4 and 1, which point to the newest 1.4.x and 1.x.y. Never deploy production by the latest tag: it changes under you. An image digest (sha256:...) is the fully immutable name of an image.

bash
GIT_SHA=$(git rev-parse --short HEAD)          # for example 9f3c2ab
docker build -t registry.example.com/shop-api:sha-$GIT_SHA .
docker tag registry.example.com/shop-api:sha-$GIT_SHA registry.example.com/shop-api:1.4.2
docker push registry.example.com/shop-api:sha-$GIT_SHA
docker push registry.example.com/shop-api:1.4.2
One build, two tags: the SHA for tracing, the semantic version for people.

Pipeline speed

  • Cache dependencies keyed on the lock file hash, so unchanged dependencies are not downloaded again.
  • Run independent jobs in parallel (lint, unit tests, image scan) and split big test suites across several runners.
  • Use Docker layer caching: copy the lock file and install dependencies before copying the source code.
  • Run only what changed in a large monorepo, using path filters.
Careful: Common mistakes. (1) Treating 100% coverage as the goal and writing tests without real checks. (2) Re-running flaky tests until green instead of fixing them. (3) Overwriting an artefact version with new content, so "1.4.2" means two different things. (4) Deploying with the latest tag. (5) Too many slow end-to-end tests and too few unit tests, which makes the pipeline slow and fragile (the "ice-cream cone" shape).

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Testing, quality gates and artefacts · 1/7

Testing and artefacts

In this lesson you will learn how tests and quality gates protect a pipeline, and how to store and tag what you build.

Prefer a video by a teacher? Find free videos on “test pyramid ci pipeline quality gates artifacts semantic versioning” on YouTube ↗

In real life: Quality gates at an e-commerce company during sale season

Before a big festive sale, an e-commerce team in Gurugram tightens its pipeline. Every pull request must pass 3,000 unit tests and 150 integration tests, keep line coverage at or above 80%, show zero ESLint errors and have no critical vulnerabilities in the image scan. Five checkout E2E tests run on staging after every merge. A flaky payment-page test is quarantined with an owner and a two-day deadline instead of being re-run until green. Each image is tagged with its git SHA and a semver release tag, so when an issue appears on sale day, the team knows exactly which commit is live and can redeploy the previous tag in minutes.

Practice (0/3 done)

Task 1 · Question

Which kind of test should a healthy test suite have the MOST of?

Task 2 · Code · Python

Write quality_gate(report, min_coverage=80). report is a dict like {"tests_total": 120, "tests_failed": 0, "coverage": 84.5, "critical_vulns": 0, "lint_errors": 0}. Return {"passed": bool, "reasons": [...]}, adding these reason codes in this order: "tests" if any test failed OR no tests ran at all, "coverage" if coverage is below min_coverage, "critical_vulns" if there is at least one critical vulnerability, "lint" if there is at least one lint error. passed is True only when there are no reasons.

Task 3 · Code · Python

Write image_tags(version, sha) that returns the tags to push for a release. Always start with the full version and end with "sha-" plus the first 7 characters of the commit SHA. For a normal release like "1.4.2", also add "1.4" and "1" in between. For a pre-release (a version containing "-", like "2.0.0-rc.1"), add no moving tags.

Quick quiz

Question 1

1.Why is "100% coverage" a poor goal on its own?

Question 2

2.What should you do with a test that fails randomly about once in ten runs?

Question 3

3.What does "immutable artefact" mean?

Question 4

4.Under semantic versioning, a release adds a new feature without breaking anything. Version 2.3.1 becomes:

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why is "100% coverage" a poor goal on its own?

Answer: Tests can run every line without checking the results, so the number says little about quality

Coverage shows what ran, not what was verified. Use it as a signal.

What should you do with a test that fails randomly about once in ten runs?

Answer: Fix it soon, or quarantine it with an owner and a ticket

Quarantine keeps the pipeline trustworthy while someone fixes the real cause.

What does "immutable artefact" mean?

Answer: Once version 1.4.2 is published, its content never changes

A fix means a new version, so a version number always means one exact build.

Under semantic versioning, a release adds a new feature without breaking anything. Version 2.3.1 becomes:

Answer: 2.4.0

A new backward-compatible feature bumps MINOR and resets PATCH to 0.

In the testing pyramid, which kind of test should you have the most of?Related

Answer: Unit tests

Unit tests are fast and cheap, so you can have hundreds. End-to-end tests are slow, so keep only a few key flows.

From Testing a full-stack app

Which binding must integration tests initialise?Related

Answer: IntegrationTestWidgetsFlutterBinding

Call ensureInitialized() first.

From Integration tests and CI

What does the test pyramid recommend?Related

Answer: Mostly unit tests, fewer integration tests, few UI tests

Fast, focused tests form the base.

From Testing basics and the test pyramid

In the test pyramid, which kind of test should you have the most of?Related

Answer: Unit

Unit tests are fast and cheap, so you can have many. E2E tests are slower, so keep a few for key journeys.

From Testing basics: unit, integration and end-to-end

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “CI/CD pipelines” before its exam: Deployment strategies, feature flags and GitOps.
  2. Module 6 exam: CI/CD pipelines · 12 questions · 20 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Infrastructure as code with Terraform. 53 topics left in the course before the final exam.

← Previous: Jenkins pipelines