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.
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 with
pytest app.py where app.py is the module's name. Running it will result in an error that looks like the below.
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
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.
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 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.
This time you can also check all our tests passing without any errors.
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.
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.
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.
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'
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.
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.