You change one line in your project. It seems fine, so you move on. Two days later something far away is broken, and you have no idea which change did it. Continuous integration, usually shortened to CI, is a habit and a tool that fixes this. Every time you share a change, a robot checks it for you.
CI is a computer, often a rented one in a data centre, that waits for you to push code to a shared place such as GitHub (pushing means uploading your commits). When a push arrives, the computer starts from a clean slate and runs a list of steps you wrote down. The list is called a pipeline. A typical one has four stages:
| Stage | What happens |
|---|---|
| Checkout | Download a fresh copy of your code. Nothing from your own laptop comes along. |
| Install | Install the libraries your project needs, the same way every time. |
| Test | Run your tests: small programs that call your code and compare the answer with what you expected. |
| Deploy | If everything above passed, put the new version where users can reach it. Many projects skip this stage and only check. |
The key rule: a stage that fails stops the pipeline. Later stages are skipped. That is the whole point, a broken change never reaches the deploy step.
Below is a function that should take a price and a percentage and return the discounted price. It starts with a bug. Press git push and watch the pipeline run for real, the tests are executed right here in your browser. Then switch to the fixed version, or edit the code yourself, and push again.
Waiting for a push...
Notice something sneaky if you push the buggy version: one of the three tests passes anyway. 50% of 80 happens to be 40, and so is 80 minus 50% of 80. A bug can hide behind a lucky example, which is why tests usually try several inputs, including boring ones like zero.
A test does not need a framework to be understood. It is a question with a known answer. Here is the same idea in plain JavaScript, which you can run with Node:
function discount(price, percent) {
return price - (price * percent) / 100;
}
const tests = [
["10% off 100 is 90", f => f(100, 10) === 90],
["0% off 50 is 50", f => f(50, 0) === 50],
["50% off 80 is 40", f => f(80, 50) === 40],
];
for (const [name, t] of tests) {
console.log(t(discount) ? "PASS" : "FAIL", name);
}
Running that prints:
PASS 10% off 100 is 90
PASS 0% off 50 is 50
PASS 50% off 80 is 40
With the buggy one-line body (return (price * percent) / 100) the first two lines print FAIL. Real projects use test libraries that make this tidier and exit with an error code when anything fails. That error code is what CI listens to: a program that finishes with code 0 means success, anything else means failure.
You tell a CI service what to do with a small text file kept inside your project. On GitHub this is a YAML file in the folder .github/workflows/, and the feature is called GitHub Actions. A minimal one looks like this:
name: tests
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: node test.js
Read it top to bottom: "when someone pushes, start a job on a fresh Ubuntu Linux machine, download the code, then run node test.js." The version tag after checkout@ changes over time, so check the current one in GitHub's documentation when you write your own. Other services, such as GitLab CI, CircleCI and Jenkins, use different file formats but the same idea.
You may have heard "but it works on my machine." Your laptop has years of leftovers: a library you installed once and forgot, a setting you changed, a file that only exists locally. CI starts empty every time, so if your project only works because of a leftover, the robot finds out before your users do. A failing CI run is not an insult. It is the cheapest moment to learn about a problem.
CI means: push, then a fresh computer runs your checks, then you get a pass or a fail. You don't need it on day one. But the first time you have a project with a few tests, adding a short workflow file is a small step that makes every later change safer. Start by writing one test, run it yourself, and then let the robot run it for you.