In this course we have been running tests by invoking pytest
from the command line. Every time we make changes to the code we manually re-run the tests in order to check that our changes haven't broken anything.
In a software project there might be many developers working on different parts of the code remotely, then pushing their changes (commits) to a centralised repository, such as one on GitHub. How can we make sure that tests are run whenever a new commit is received and that we are notified whenever a change breaks the code?
When running tests on your local machine you can be confident that the code is working in your environment, i.e. your operating system, version of python, etc. How can we be sure that the code will work on a range of environments?
Continuous integration (or CI) is the practice that attempts to solve these problems. The idea is that every time a new commit is pushed a CI server sets up a clean environment, does a git clone
of your code from the central repository, then attempts to build it and run any tests.
There are many fantastic free CI services. In this course we will use Travis since it integrates well with GitHub. (You should already have a GitHub account that is linked to Travis. If not, set one up now.)
Central to using Travis is the .travis.yml
file. This is a hidden configuration file (as indicated by the .
at the start of the file name) that sets options for the build. (The extension .yml
indicates that this is a YAML file, which is a recursive acronym for "YAML Ain't Markup Language".)
A typical travis.yml
file for a python build looks something like the following:
yml
language: python
python:
- "3.5"
- "3.6"
script:
- pytest
At the top of the file the language
keyword is used to indicate that this is a python
codebase. Next we specify which versions of python we want to use for our builds. Finally we come to the script
section, which specifies the commands that will be run once the build environment is set up and our code has been cloned from GitHub (which will be done automatically). Full details about customising builds can be found here.
(Note that there is an inconsistency with the naming of the pytest
executable for the different versions of python provided by Travis. This makes it hard to use the same script
section for a wide range of python versions. See here for details.)
We'll now walk through the process of running your first CI build. Before starting you'll need to open a terminal by locating the Home
Jupyter tab, clicking on the New
dropdown button near the top right, then selecting Terminal
from the list. For convenience, it might be preferable to split your screen so the terminal tab is next to this one. That way you can follow through the tutorial as you execute various commands.
First move into the grid
directory. This has already been set up with a .travis.yml
file and a GitHub README.md
file.
cd grid
Edit README.md
so that both instances of USERNAME
are replaced by your GitHub username.
Also edit grid.py
to re-introduce the bug that you fixed earlier. (Replace h-1
with w
on line 133.) Run pytest
to check that the tests fail.
pytest
Now initialise a new git
repository.
git init
Add all of the files in the directory and stage them for the commit.
git add .
Commit the files that you've staged in your local repository.
git commit -m "First commit."
Go to your GitHub profile page and create a new repository called grid
. (It's important that you use this exact name!) To do so click on the Repositories
tab, then the New
button. Leave the check box for "Initialize this repository with a README" unchecked.
We now need to add Travis integration to the new repository. If you go to your Travis homepage you should see a list of your GitHub repositories. If not, click on the Sync account
button in order to refresh the list. (It can sometimes take a couple of minutes for a new repository to appear.) Once you see the grid
repository, click on the slider to enable the CI hook.
Back on the command line we can now add a URL for the new remote repository that you just created. Once again, replace USERNAME
with your actual GitHub username.
git remote add origin remote https://github.com/USERNAME/grid.git
You can now push your commit to the remote repository.
git push origin master
Since we enabled a build hook for the grid
repository, Travis will detect the presence of the .travis.yml
file in your new repository and automatically initiate a build. If you visit the GitHub page for the repository you will see a Travis build status image on the main page.
Clicking on this will take you to the Travis page for the repository, where you can see the progress of the current build, as well as the details of any previous builds. You should see the status reported as failed. In addition, you might also receive an email notifying you of the error.
(It's normally bad practice to push code that you know is broken. In this case we're using it as an example to show how to go about fixing it in the correct way.)
On the GitHub page for the grid
repository you can open an issue to alert people that the code is broken. Click on the Issues
tab followed by the New issue
button. Give your issue whatever title you like, then hit submit. It's good practice to give a minimal example that illustrates the problem. This helps the owner of the repository to reproduce the problem. You could also provide a new unit test if none of the current ones trigger the bug. In this case, we already have a good test that catches the error.
Back in your local repository fix the bug that you introduced earlier and verify that the tests now pass.
Having done this you can stage the grid.py
file, then commit the change. For simplicity we'll do this in a single step.
git commit grid.py -m "Fixed a bug affecting cells at the top of a grid. [closes #1]"
Now push the commit to GitHub.
git push
The commit will now appear on GitHub and Travis will run another build using the updated version of the code. Once the build is complete you should hopefully see a green status badge on the repository homepage to indicate that it passed.
Take another look at the Issues
tab. You should see that the issue that you opened is now closed. This happened automatically because we included the phrase "closes #1
" somewhere in our commit message. Here #1
is the issue number, i.e. the first issue that was opened.
(It is often helpful to put CI flags in brackets at the end of a commit message. This way it keeps the output of git log
clean and it is easy to search for them.)
(If you find a bug in someone else's code then you should make a fork of the repository, fix the bug, then create a pull request.)
Sometimes you might commit changes that don't affect the functionality of the code, e.g. comments, or changes to the README.md
file. In this case there is no need to run another CI build since none of the changes will affect the result of the tests. Since a build can be a time consuming process it would be wasteful to run one if wasn't absolutely necessary.
Thankfully there is a flag that can be added to commit messages in order to indicate that a CI build should be skipped, ci-skip
.
Edit the README.md
file to include a new line saying "Testing is great!" (or whatever you prefer). Now commit your changes.
git commit README.md -m "Updated the README. [ci skip]"
Finally push the changes to GitHub.
git push
If you go to the GitHub or Travis page for your grid
repository you should find that there wasn't a third CI build. On the GitHub page you can click on where it says "3 commits
" to show the commit history. There should be a red cross (failed) next to the first commit, a green tick (passed) next to the second, and nothing (skipped) next to the third.
.travis.yml
file to be added to you repository[ci skip]