This lesson is being piloted (Beta version)

Continuous Integration (CI)

Overview

Teaching: 15 min
Exercises: 10 min
Questions
  • How can I run tests automatically?

Objectives
  • Short introduction to Continuous Integration.

  • Setting up Continuous Integration for a Python project with GitHub Actions.

  • Additional examples for GitLab-CI and Bitbucket pipelines.

Devising a number of automated tests that are testing all “bits of code” if everything is working correctly – called Unit Tests – has become a good practice in software development.

But having a large set-suite is only helpful when you run it in it’s entirety on a regular basis. Otherwise you risk to miss side-effects of your changes for too long and it becomes more difficult to track down which change caused the test(s) to break when there have been too many changes since the test was last used.

The practice that is designed to help with that is called Continuous Integration and is abbreviated as CI. CI automates building and/or running a test-suite whenever changes are pushed to the repository on the server or whenever a pull request is opened.

Continuous Integration is often combined with Continuous Delivery (CD), where changes to a software are put into production either fully automatically, once all tests have passed, or semi-automatically where a designated developer just has to trigger the otherwise fully-automatic roll-out.
This could either mean automatically deploying code on a server whenever code has changed or packaging and uploading uploading release-bundles anytime a new version is tagged – however always under the condition that all tests are passing.
Together they are commonly referred to as a CI/CD Pipeline.

The three major Git-Hosting websites have CI/CD-pipelines directly built-in: GitHub Actions, GitLab-CI and Bitbucket Pipelines. There are also external services like Travis-CI and Circle-CI.

All of those services count the number of minutes these pipelines run on their servers and have different restrictions on how long the service can be used for free and from which point a project has to pay:

(as of October 2021)

Both GitHub and GitLab also allow you to use your own GitLab runners that are running on your computer. Using private runners is free of charge and the minutes that these runners are working are not billed.

These three CI-pipeline services have in common that they are configured with a single text file in the YAML format, which for GitHub Actions is placed in a directory called .github/workflows and for other services into the root directory of a project with a specific name: .gitlab-ci.yml for GitLab, bitbucket-pipelines.yml for Bitbucket and .travis.yml for Travis-CI.

These files need to contain information about all steps that are needed to test and optionally deploy the software. Typical steps include:

However not all of the steps are needed or desired in all cases.

Example files

Each of the CI services introduced here uses YAML as the file format – a way of encoding nested lists and dictionaries – in which the pipelines are described, however everyone uses a different set of rules about how certain elements should be named and how they should be nested. All services provide a good number of example files for several supported languages which can be used as a starting point.

The examples below all do basically the same thing: running the unit-tests of a Python project after installing all dependencies. The GitHub and GitLab examples run the tests with two versions of Python (2.7 and 3.8) the Bitbucket example only tests with Python 3.8.

GitHub Actions

GitHub Actions Quickstart

# .github/workflows/github-actions.yml
name: learn-github-actions
on: [push]
jobs:
  test-my-python-code:
    # select the os-image these jobs should run on
    runs-on: ubuntu-latest
    
    # define the Python versions that should be used
    strategy:
      matrix:
        python-version: [2.7, 3.8]

    steps:
      # step to check out the repository
      - uses: actions/checkout@v2

      # step to create the Python environment
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      # step to install the dependencies
      - name: Install dependencies
        run: "pip install -r requirements.txt"

      # step to run tests
      - run: pytest -v

GitLab-CI

GitLab-CI Quickstart

# ./.gitlab-ci.yml
test:2.7:
  image: python:2.7
  # step to install dependencies
  before_script:
    - python -V
    - pip install -r requirements.txt
  # step to run tests
  script:
    - pytest -v

test:3.8:
  image: python:3.8
  # step to install dependencies
  before_script:
    - python -V
    - pip install -r requirements.txt
  # step to run tests
  script:
    - pytest -v

Bitbucket-Pipeline

Getting Started with Bitbucket Pipelines

# ./bitbucket-pipelines.yml
image: python:3.8
pipelines:
  default:
    # step to install dependencies
    - step:
        name: install dependencies
        caches:
          - pip
        script:
          - python -V
          - pip install -r requirements.txt
    # step to run all tests
    - step:
        name: run all tests
        script:
          - pytest -v

Getting Started with GitHub Actions

Let’s get our feet wet with GitHub Actions.

Getting Started with GitHub Actions

  1. Go to the cloned testing_demo repository on your computer.
  2. Create the .github and .github/workflows directories:
    mkdir .github
    mkdir .github/workflows
    
  3. Copy the prepared more_files/github-actions.yml file to the .github/workflows directory, as well as the requirements.txt to the root, then commit the change and push to GitHub:
    testing_demo $  cp  more_files/github-actions.yml  .github/workflows
    testing_demo $  cp  more_files/requirements.txt  ./
    testing_demo $  git add .github/workflows
    testing_demo $  git add requirements.txt
    testing_demo $  git commit -m "add GitHub Actions"
    testing_demo $  git push origin main
    
  4. Go to the “Commits” page of your repository on GitHub.
    • a yellow indicates testing is in progress,
    • a green indicates all tests passed.
    • a red indicates that there are failed tests,
  5. Refresh the browser every 10 seconds until a appears.
  6. Click on the red next to the latest commit, and then on “Details”.
  7. Try to fix the error in the Python code, commit and push the change and then see if the tests pass.

A Second Look at the Pipeline definitions

Let’s have another look at the pipeline definitions to see what they are actually doing.

GitHub Actions revisited

Let’s go over this workflow line by line:

name: learn-github-actions
on: [push]
jobs:
  test-my-python-code:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [2.7, 3.8]

    steps:
      - uses: actions/checkout@v2

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

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

      - run: pytest -v

Other CI solutions

There are also other Build/CI/CD services and tools that can be use as an alternative to those provided by the Git-Hosting services.

The cloud services Travis-CI and Circle-CI work similar to the methods described above, in that they are also controlled by YAML files that are added to the repositories. The only additional steps that are needed is to sign up for an account with the service and to link the cloud-service to the repository in the project’s settings menu under “Integrations”.

Jenkins and other similar tools, been around much longer than the services described above, and it requires that you can install on your own machine and can be used with any version control solution. Running the pipelines can then either be triggered by web-hooks (the Source code repository calls a certain URL when a new commit has been pushed) or on a defined schedule (cron-job). Jenkins also doesn’t use YAML to describe the pipelines, but has its own “Domain Specific Language” for it’s Jenkinsfiles. Also in terms of their use however is quite different from the services introduced above and they are beyond the scope of this lesson.

Key Points

  • Running automated tests often helps tracking down bugs early.

  • With CI, tests are being executed anytime changes are pushed to the repository.

  • CD is used to automate the deployment of new releases.