Skip to content

Grokem · https://www.grokem.com/devops-and-platform/ci-cd/github-actions-python-tests

CI/CDTutorial

Run your tests on every pull request with GitHub Actions

One YAML file turns your test suite into a check that runs on every push and pull request, on several Python versions, with dependency caching.

Grokem Editorial

Editorial desk

Published
  • Beginner
  • 20 min to complete
Tested withGitHub Actionshosted runners (ubuntu-latest)Python3.10 to 3.12pytest7 or newer

Before you start

  • A GitHub repository with Python code and pytest tests (see the pytest guide)
  • A requirements.txt that lists pytest and your dependencies
  • Permission to add files to the repository

Continuous integration (CI) means every change is built and tested automatically before it is merged. With GitHub Actions the whole setup is one file in your repository.

What you will build

A workflow that, on every push to main and on every pull request:

  1. checks out your code,
  2. installs Python (three versions, in parallel),
  3. installs your dependencies, with caching,
  4. runs pytest.

Step 1: Create the workflow file

GitHub reads workflows from .github/workflows/. Create .github/workflows/tests.yml:

yaml
name: tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip

      - name: Install dependencies
        run: python -m pip install -r requirements.txt

      - name: Run the tests
        run: python -m pytest -q

Step 2: Understand each part

  • on: lists what triggers the workflow. A push to main and any pull request is a sensible default. Without pull_request you would only learn a branch is broken after it is merged.
  • runs-on: ubuntu-latest picks the machine image. Each job gets a fresh machine, so nothing carries over from your laptop. That is the point: it shows whether your project works from a clean start.
  • strategy.matrix repeats the job for each Python version, in parallel. fail-fast: false lets the others finish when one fails, so you see the whole picture at once.
  • actions/checkout@v4 downloads your repository onto the runner.
  • actions/setup-python@v5 installs the requested Python. cache: pip stores downloaded packages between runs, which speeds up later ones.
  • run: executes a shell command. A command that exits with a non-zero status fails the step and the job, which is how pytest reports failing tests.

Step 3: Make sure requirements.txt is complete

CI starts from nothing, so every package the code and tests import must be in requirements.txt:

text
pytest>=7
requests>=2.31

The most common first failure is a ModuleNotFoundError for something that is installed on your own machine but was never written down.

Step 4: Push and watch it run

Commit the workflow and push it, or open a pull request. In the repository, open the Actions tab: you will see the run named tests with one job for each Python version. Open a job to read its log, line by line, as it ran.

A red cross means a step failed. The log shows which one, and for a test failure it contains the same pytest report you see locally.

Step 5: Make the check count

A check that people can ignore is a suggestion. In the repository's settings, add a branch protection rule (or a ruleset) for main that requires the status checks to pass before merging. From then on a pull request with failing tests cannot be merged by accident.

Good habits

  • Keep the pipeline fast. Developers stop waiting for a ten-minute check and stop trusting it. Cache dependencies and run the slowest tests separately.
  • Keep secrets out of the file. Use the repository's encrypted secrets and read them through ${{ secrets.NAME }}; never write a key into a workflow.
  • Treat a flaky test as a bug. A test that fails now and then teaches everyone to rerun and ignore failures.
  • Run the same command locally. If python -m pytest -q is what CI runs, it is also what you run before pushing.

Workflow syntax and the available actions change over time, so check the linked documentation when you build your own.

Expected result

The Actions tab shows a run named 'tests' for each push and pull request, with one job per Python version, each ending in a green tick when pytest passes.

Troubleshooting

ProblemLikely causeFix
The workflow does not run at allThe file is not in .github/workflows/, is not valid YAML, or its on: section does not match what you did.Check the path and the file extension (.yml or .yaml). Look at the Actions tab for a parsing error.
ModuleNotFoundError in CI but not on your machineThe package is installed locally but not listed in requirements.txt.Add it to requirements.txt, ideally with a version, and push again.
A job fails only on one Python versionThe code or a dependency uses something not available on that version.Fix it, or remove that version from the matrix if you do not support it.
Tests pass in CI but the pull request can still be merged when they failThe check is not marked as required.In the repository settings add a branch protection rule (or ruleset) and require the check to pass.

Frequently asked questions

Does this cost money?

Public repositories get free usage. Private repositories get a monthly allowance of free minutes that depends on your plan, and beyond it minutes are billed. Check the current limits in GitHub's documentation.

Why pin the action versions (v4, v5)?

A version tag means your pipeline does not change under you when an action publishes a new major version. For stricter control, some teams pin an action to a full commit hash.

Next steps

  • Add linting (for example ruff) as a second job
  • Upload test reports or coverage as artifacts
  • Require the check in a branch protection rule

References

From Grokem: https://www.grokem.com/devops-and-platform/ci-cd/github-actions-python-tests

How did this strike you?

Be the first to react – it is anonymous.

Community

Questions about this?

Ask in Questions and help and get help from other readers.

Ask a questionBrowse Questions and help

Comments (2)

  • Tom

    Can the workflow cache the pip downloads to run faster?

    • Grokem Editorial

      Yes. The `actions/setup-python` step accepts `cache: pip`, which stores the downloaded packages between runs and refreshes them when your requirements file changes.

Choose what we may use. You can change this at any time with “Privacy choices” at the foot of every page. Details are in the Cookie policy and the Privacy policy.

Strictly necessary

Remembers your theme, the lessons you marked as done and this choice. It stays on your device, tracks nothing and cannot be switched off.

Always on
What exactly is collected, and why

Grokem’s own counts

Collects
The pages you open and how long you stay, how far you scroll, where you came from, your device type, browser, screen size, language and time zone. Your IP address is turned into a code that changes every day; it is never stored as it is.
Why
To see how long lessons are read and where readers leave. Not shared with anyone.
Stored
A random visitor ID, a visit ID and the time of your last page view in your browser’s storage (gr_vid, gr_sid, gr_last). No name or email is attached.