How to Run Python Tests on Every Commit Using GitHub Actions

Jan. 10, 2022

How to Run Python Tests on Every Commit Using GitHub Actions

It's easy to avoid bugs than fix them.

Testing is a critical component of every software project. Among several different types of software testing, some need to run at the time of every commit. For instance, developers should run unit tests to ensure their new change doesn't contradict any previous ones.

Yet, it is a burden to do it manually every time. It's a dull, repetitive, but unavoidable task. It's fun writing unit tests but not running them on every commit.

Most dev teams use their continuous integration (CI) pipeline to trigger them. Thus, the developer doesn't have to worry about not running them. Github Actions are one (and perhaps very popular) such options available to do this.

In this post, I'll walk you through the steps I follow to automate Python tests with Github Actions.

How Python testing works on GitHub Actions

GitHub Actions has a pre-defined set of workflows you can plug and play. You could find one for popular frameworks such as Django too. I recommend starting with one of them unless your project structure is unique.

You can get there by clicking on the Actions tab on your GitHub repository page.

You can find GitHub actions at the top of your GitHub repository page. You can start configuring your workflows here.

For this post, I will use the following example Python program and test.

# testing Fibonacci number function
def fib(n: int) -> int:
    return n if n < 2 else fib(n-1)+fib(n-2)


def test_fibonacci():
    assert fib(10) == 54

We can run the above code in our local computer withpytest app.py where app.py is the module's name. Running it will result in an error that looks like the below.

Pytest assertion fails for Fibonacci number generation function.

Now, let's take this test to run on GitHub Actions. Do the following to make the project a git repository and push it to GitHub.

# Create a requirement file to install dependencies whenever GitHub Actions runs the tests.\
pip freeze > requirement.txtgit init\
git add .\
git commit -m 'initial commit'# Create a remote repository in github.com and do the following.\
git remote add origin <Your Repository>\
git branch -M main\
git push -u origin main

Your remote repository will contain two files; the requirement.txt and the app.py. Go to the Actions tab and select Python application.

This will open a new file called python-app.yml in the .github/workflow folder. This is where you can configure events, actions, etc., and their sequence.

The default file is pre-configured to run a set of actions whenever we push changes to the main branch or make a pull request. It runs on an Ubuntu container with Python 3.10. After installing the dependencies from the requirement.txt file, it executes the pytest shell command.

Please change the last line from pytestto pytest app.py and click on the start commit button on the top right corner.

As we've configured it to run on every push, you don't have to trigger any action manually. If you go to the Actions tab now, you can see the new workflow we've created already started to perform the tests.

GitHub action upon creation will automatically start performing tests.

As expected, it'll fail at first. Click on the new workflow run, and you can see why it failed. It's the same error we had when running it locally.

Github Actions running pytest tests and showing error in the workflow run logs.

GitHub also sends you an email whenever new changes fail to pass tests. If you're following this guide, do check your inbox associated with your GitHub account.

Let's try editing it locally and push the code that passes all the tests. Change the value in the assertion to 55 from 54, as shown below.

# testing Fibonacci number function
def fib(n: int) -> int:
    return n if n < 2 else fib(n-1)+fib(n-2)


def test_fibonacci():
    assert fib(10) == 55 # It was 54 before

Now commit and push these changes to the remote repository. On the Actions tab, you can see a new workflow run is running now.

GitHub Action triggers Python tests when a new code is pushed to the remote repository.

This time you can also check all our tests passing without any errors.

GitHub action performs Python tests every time we push changes to our codebase. When there is no issues, it shows all tests are passed.

Using environment variables in GitHub Actions.

Most python projects contain environment variables. These are specific variables unique to every deployment. This file usually doesn't go to the remote repository.

Related: 7 Ways to Make Your Python Project Structure More Elegant

For example, you may define the port to start your Python-based web application in a .env file. But committing this to a remote repository is not needed as other users may use a different port.

To specify an environment variable for a GitHub workflow, we must edit the YAML file again. After setting the OS to look like the following, let's add a couple of new lines.

jobs:
  build:

    runs-on: ubuntu-latest
    env:
      FIB_INPUT: 10

Let's update our code to read the input variable from the environment file instead. It should look like this.

import os

# testing Fibonacci number function
def fib(n: int) -> int:
    return n if n < 2 else fib(n-1)+fib(n-2)


def test_fibonacci():
    assert fib(int(os.environ["FIB_INPUT"])) == 55  # It was 54 before

Let's commit and see if the test passes on the GitHub Actions workflow.

git commit -am "Read inputs from env variables"
git push

If you navigate the GitHub Action ad the respective workflow run, you can now see the test passing.

Python test function read the input value we defined in an environment variable. Test passed without any issues.

Test function read inputs from environment file and performed successfully --- Screenshot by author.

Changing triggers to workflow runs

It may be a little too much to perform a full round of testing every time someone commits a change to the repository. We may want to narrow it down to a single branch or so.

We can do this and many other customizations through the YAML file. Here in the below example, we're configuring to run the tests on every push to the dev branch and every pull request to the main branch. It's a practice most developers follow too.

on:
  push:
    branches: [ dev ]
  pull_request:
    branches: [ main ]

To learn more about different ways you can customize triggers, please consult GitHub Actions' documentation.

Related: Git Pre-commit Hooks for Automatic Python Code Formatting

Scheduling tests

It's also very common to run tests on a schedule. It may be the only practice for some teams. Others may prefer to do it in parallel to tests on every push.

We can schedule workflows to run on specific rutine in Github Actions. If you are familiar with corn jobs, you can apply the same here in the YAML file.

Here's an example that runs our tests every Sunday at midnight.

on:
  schedule:
    - cron:  '0 0 * * 0'    

Final thoughts

CI pipelines are a revolutionary step in DevOps. Performing tests in a CI pipeline avoided the chances of introducing bugs into the system.

We can use GitHub Actions as a perfect CI workflow. Here in this post, we've discussed how to use it to perform Python tests before pushing any changes to the repository.

We've also discussed how to schedule workflows in cronjobs and customize the event triggers. We may have to be very specific about when we are running or workflows. Because running comes with its costs.

GitHub Actions for basic use is free up to 2000 minutes of system time. It applies even if yours is a private repository. But beyond this limit, you need one of their paid plans. However, if the repository is a public one or if you use your private runner, it's completely free.


Thanks for the read, friend. It seems you and I have lots of common interests. Say Hi to me on LinkedIn, Twitter, and Medium. I’ll break the ice for you.

Not a Medium member yet? Please use this link to become a member because I earn a commission for referring at no extra cost for you.

How we work

Readers support The Analytics Club. We earn through display ads. Also, when you buy something we recommend, we may get an affiliate commission. But it never affects your price or what we pick.

Connect with us