Your first GitHub Actions workflow: run tests on every push
Build a working GitHub Actions pipeline from nothing: a small Python project with a test, a workflow file that runs pytest on every push and pull request, a deliberate failure to see the red cross, and a matrix.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
What you will build
A GitHub repository with a tiny Python module, one pytest test, and a workflow that installs dependencies and runs the tests on every push to main and on every pull request. You will watch it pass, break it on purpose to see how a failure looks, fix it, and finally extend it to test on two operating systems and two Python versions at once. The same shape works for any language; only the install and test commands change.
Before you start
- A GitHub account and
gitinstalled locally, with push access over SSH or HTTPS. - Python 3 installed locally so you can run the test yourself before CI does.
- About 20 minutes. GitHub Actions is free for public repositories and has a free monthly allowance for private ones.
Step 1: create the project
mkdir ci-demo && cd ci-demo
git init -b main
mkdir calc tests
printf 'def add(a, b):\n return a + b\n' > calc/__init__.py
printf 'from calc import add\n\n\ndef test_add():\n assert add(2, 3) == 5\n' > tests/test_calc.py
echo 'pytest==8.4.1' > requirements.txt
python3 -m venv .venv && . .venv/bin/activate
python -m pip install -r requirements.txt
python -m pytest -v
The last command should print 1 passed. Add a .gitignore with .venv/ and __pycache__/ so the virtual environment is never committed.
Step 2: write the workflow
GitHub looks for workflows in .github/workflows/. Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: pip
- name: Install dependencies
run: python -m pip install -r requirements.txt
- name: Run tests
run: python -m pytest -v
onlists the events that start the workflow: pushes tomainand any pull request.jobs.testis one job;runs-on: ubuntu-latestasks for a fresh Ubuntu virtual machine from GitHub.- Each
usesstep runs a reusable action:actions/checkoutclones your repository into the runner,actions/setup-pythoninstalls the requested Python and, withcache: pip, caches downloaded packages between runs. - Each
runstep is a shell command, exactly what you typed locally. If any step exits non-zero, the job fails and later steps are skipped.
Step 3: push it to GitHub
Create an empty repository on GitHub named ci-demo (no README, so the first push is clean), then:
git add .
git commit -m "Add calc module, test and CI workflow"
git remote add origin [email protected]:YOUR-USER/ci-demo.git
git push -u origin main
Open the repository in the browser and click the Actions tab. A run named after your commit message appears within seconds, first with a yellow dot, then a green check. Click it, then the test job, to see every step’s output, including the pytest line 1 passed.
Step 4: break it on purpose
Seeing a failure once, in a safe setting, makes the real ones much less alarming. Change the expected value in the test and push on a branch:
git switch -c break-the-build
sed -i.bak 's/== 5/== 6/' tests/test_calc.py && rm tests/test_calc.py.bak
git commit -am "Break the test on purpose"
git push -u origin break-the-build
Open a pull request from break-the-build to main. At the bottom of the pull request the check CI / test turns red; Details leads to the log where pytest shows assert 5 == 6. Fix the test (change 6 back to 5), commit and push again; the check re-runs on its own and turns green. Then merge the pull request from the web interface, or close it and delete the branch.
Step 5: test on more than one platform
Replace the job with a matrix so the same steps run for each combination of operating system and Python version. Edit .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
- run: python -m pip install -r requirements.txt
- run: python -m pytest -v
fail-fast: false lets all four combinations finish even if one fails, so you see the full picture. Commit and push; the Actions tab now shows four jobs named test (ubuntu-latest, 3.12) and so on, running in parallel.
Check that it works
- The Actions tab shows a green run for your latest push on
main, with four matrix jobs. - The commit list on the repository front page shows a green check next to the newest commit.
- Opening a pull request shows the CI checks and blocks the merge button while they run.
- Optional: in Settings > Branches add a protection rule for
mainthat requires the test checks to pass before merging.
Clean up
Nothing runs unless you push, so an idle repository costs nothing. If you do not want to keep it, delete the repository under Settings > General > Delete this repository, and remove the local folder with rm -rf ci-demo.
Next steps
- Add a linting step before the tests, for example
pip install ruff && ruff check ., so style problems fail fast. - Read the GitHub Actions cheat sheet for triggers, secrets, caching and job dependencies.
- Run the same hooks locally before every commit with pre-commit, so CI rarely has to tell you about trailing whitespace.
- Try the workflow locally without pushing using nektos/act.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0039 A minimal GitHub Actions workflow that runs your tests Add one YAML file under .github/workflows and GitHub runs your tests on every push and pull request. Here is a small, explained… Intermediate · Step by step · Any system Article GitHub Actions 15 Oct 2026
- 0256 Manage infrastructure as code with Terraform: from first file to plans on pull requests Learn the full Terraform workflow without a cloud account: variables, a module, outputs, state, plan, apply and destroy, a backend for shared… Beginner · Guided project · Linux, macOS, Windows Article GitHub Actions · Terraform 15 Oct 2026
- 0313 What is CI/CD? CI/CD is the habit of automatically testing every code change and, once it passes, deploying it without manual steps. Beginner · Explains an idea · Any system Article Pipelines, runners & hooks 15 Oct 2026
- 0378 Continuous integration explained What continuous integration means, why small frequent merges plus an automated build on every change prevent integration pain, what a good pipeline… Beginner · Explains an idea · Any system Article Pipelines, runners & hooks 15 Oct 2026
- 0380 GitHub Actions cheat sheet: triggers, matrix, secrets, caching The GitHub Actions syntax you reach for most, in copy-ready blocks: event triggers and filters, build matrices, secrets and permissions, dependency caching… Intermediate · Cheat sheet · Any system Article GitHub Actions 15 Oct 2026
- 0381 pre-commit hooks: catch mistakes before they are committed Install the pre-commit framework, configure hooks for whitespace, YAML and JSON syntax, large files, leaked private keys, Python linting and shellcheck, run… Beginner · Step by step · Linux, macOS, Windows Article Pipelines, runners & hooks · Setup 15 Oct 2026
Repos in the wild
- pre-commit: a framework for managing multi-language Git hooks pre-commit/pre-commit The pre-commit repository: the tool that installs, isolates and runs Git hooks declared in a YAML file, with hooks written in Python, Node, Go, Rust, Ruby, Docker and more, pinned to exact versions. GitHub
- Woodpecker CI: a lightweight, self-hosted CI engine with container-based pipelines woodpecker-ci/woodpecker The Woodpecker CI repository: a community fork of Drone that runs every pipeline step in its own container, connects to GitHub, Gitea, Forgejo, GitLab and Bitbucket, and runs on a single small server. GitHub
- act: run your GitHub Actions workflows locally with Docker nektos/act The nektos/act repository: a command-line tool that reads .github/workflows, simulates GitHub events and runs the jobs in Docker containers on your machine, so you can test a workflow without pushing. GitHub
- Gitleaks: find secrets that were committed to a Git repository gitleaks/gitleaks Gitleaks scans Git history, working trees and arbitrary files for passwords, API keys, tokens and private keys using a library of patterns, and is usually run as a pre-commit hook and in CI. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +5,819 chars