How to Run Python unittest Tests in GitHub Actions

To run Python tests in GitHub Actions, add a job that checks out the code and runs python -m unittest discover against your tests folder. Most broken test jobs have one wrong value: the start directory, the top-level directory, or the working directory. Run the job locally with act to see the error in seconds.

Start this project - freeLive cloud workspace in your browser · lands on your portfolio
DevOps Engineerci-cdgithub-actionspython

A minimal test workflow

Given this layout:

.
├── app.py
├── tests/
│   ├── __init__.py
│   └── test_app.py
└── .github/workflows/ci.yaml

The workflow:

# .github/workflows/ci.yaml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Run unit tests
        run: python -m unittest discover -s tests -t . -v

What the unittest discover flags mean

If -s points at a folder that does not exist, unittest fails with an error like ImportError: Start directory is not importable: 'app_tests'. The job fails, but the code and tests are fine - only the path in the workflow is wrong.

Step 1: read the failing step

In the GitHub Actions UI, open the failed run, then the test job, then the red step. The last lines show the error. Compare the path in the error with your repository:

ls
cat .github/workflows/ci.yaml

Step 2: run the job locally with act

Pushing a commit for every attempt is slow. act runs GitHub Actions workflows on your own machine in containers:

act push -j test

-j test runs only the test job. You see the same step output as on GitHub, in a few seconds.

Step 3: correct the path and run again

- name: Run unit tests
  run: python -m unittest discover -s tests -t .
act push -j test

A passing run ends with output like this:

Ran 2 tests in 0.001s

OK
Job succeeded

Watch out for "Ran 0 tests"

A job can be green and still test nothing. If discovery finds no files, recent Python versions exit with a non-zero code, but older versions and some other runners report success. Check the log for the number of tests. Common reasons for zero tests:

A simple guard is to look for the count in the output, or to use a coverage tool with a minimum threshold, so "ran nothing" fails the build.

Using pytest instead

pytest also runs unittest-style tests:

- run: pip install pytest
- run: pytest tests/ -v

pytest exits with code 5 when it collects no tests, so an empty run fails the job by default.

Other common causes of a failing test job

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free

What you'll practice

FAQ

How do I run Python unit tests in GitHub Actions?

Add a job with actions/checkout, optionally actions/setup-python, and a step that runs python -m unittest discover -s tests -t .. Every push then runs your tests.

What does 'Start directory is not importable' mean?

The folder passed to unittest discover -s does not exist or is not a Python package. Check the path against your repository layout.

How can I test a GitHub Actions workflow locally?

Use act. act push -j test runs the test job for a push event on your machine, using containers, without pushing to GitHub.

Why does my CI job pass but run 0 tests?

Test discovery found no files - usually a wrong start directory or a file name that does not match test*.py. Check the test count in the log and make an empty run fail the build.

Keep learning

Repair a Failing GitHub Actions WorkflowDevOps projectSpeed Up a Slow GitHub Actions PipelineDevOps projectBuild a Multi-Stage CI/CD Pipeline With CachingDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free
Start this project - free