My Python projects so far have only included me doing manual testing (i.e. running my Python program against my dev environment and see if it works). Although manual testing has its place, I have been thinking of how to introduce some automated testing via source control with continuous integration. This would allow me to introduce new commits to my code and see if it still passes tests.
Over the previous weekend (a 4 day bank holiday for a lot of people in the UK) I decided to repurpose my Raspberry Pi’s and get to it.
For this project I am using:
A Raspberry Pi 4 (4GB) to run GitLab.
A Raspberry Pi 4 (2GB) to run a GitLab Runner.
A Raspberry Pi 3 (1GB) to run Grafana and where I run my Python from.
All the Pi devices are running headless using a fresh install of Raspberry Pi OS.
A note on Continuous Integration tools
I’m going with GitLab for this project as I want to run a source control system and CI locally on my Raspberry Pi. Other tools are available for local installation (e.g. like Jenkins) and several services online / via the cloud offer source control and/or CI (e.g. GitHub, AWS CodePipeline, Azure Pipelines, CircleCI). If you have a particular favourite, or one that you’ve wanted to try then go with that.
GitLab has an official install for the Raspberry Pi and it is recommended to run it on a Pi 4 with at least 4GB. The notes for this install can be found at https://docs.gitlab.com/omnibus/settings/rpi.html , and I recommend reading them but to summarise:
- Edit /etc/dphys-swapfile to change the swap space to 4GB
- Add the Gitlab repository using: sudo curl -sS https://packages.gitlab.com/install/repositories/gitlab/raspberry-pi2/script.deb.sh | sudo bash
- Set up GitLab, replacing the URL with the devices local hostname (e.g. raspberrypi.local) using: sudo EXTERNAL_URL=”http://gitlab.example.com” apt-get install gitlab-ce
- Once installed point your browser at the device (e.g., http://raspberrypi.local) or if your not using local DNS/edited hosts file then point it at http://IP_ADDRESS_OF_DEVICE. GitLab should now ask you to set up a root account.
- After setting up root, add a second account for regular use (i.e. don’t use root for regular day-to-day activities).
Note: I’m using a personal dev environment so have not included options for SMTP (e.g via Postfix) and the HTTP options for this initial short testing. If using Gitlab long term, in a non-dev environment or using Gitlab short term for anything sensitive then please make sure to read up on and use HTTPS.
Installing GitLab Runner
A GitLab runner allows for GitLab to run commands against the code it is storing. GitLab Runners can be installed in a few different ways including containers (e.g. Docker, Kubernetes) but I’m going to install it with the shell option on a Raspberry Pi 4 (2GB).
Note: GitLab recommends installing the GitLab Runner on a separate instance to the GitLab install (e.g., don’t put them both on the same machine/instance/virtual machine).
As with the previous step, GitLab maintain guidance on officially installing a GitLab runner found at https://docs.gitlab.com/runner/install/linux-repository.html , and again I recommend reading it but to summerise:
- Add the GitLab repository using the command: curl -L “https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh” | sudo bash
- Install the latest version of GitLab Runner using the command: exportGITLAB_RUNNER_DISABLE_SKEL=true; sudo -E apt-get install gitlab-runner
Add a Project to GitLab and register the GitLab Runner
Create a new project within GitLab and then within the project use the menu on the left to find “Settings” and then “CI/CD” within “Settings”.
Within this “CI/CD” section is an option for “Runners” and within “Runners” is the URL and registration token needed to pass to your GitLab Runner. Note these down and on the Pi running the GitLab Runner use the command:
sudo gitlab-runner register –url URL_HERE –registration-token REGISTRTION_TOKEN_HERE
Replacing URL_HERE and REGISTRATION_TOKEN_HERE with appropriate values.
What About The Python Code And Tests?
GitLab and a GitLab Runner are set up, so now to add some code. As mentioned in the introduction I want to use some testing with Python and for this initial try I am going to modify my Grafana API project.
I am going to use Unit Testing. Unit Testing tests individual components of code to validate that each component works as expected. To run the tests I have created a new file called “test_main.py” within the root of the my project which imports the unittest library and contains the tests I have created. So far these are pretty simple and just check that the returned values from the functions in my Python code return values that are strings.
Here is my code for test_main.py:
import unittest import main # loading settings from .env file in root of project directory from dotenv import load_dotenv load_dotenv() class grafanaTests(unittest.TestCase): """Tests for GeekTechStuff Grafana API Python""" def test_admin_name_is_string(self): admin_username = main.get_username() self.assertIs(type(admin_username),str) def test_admin_password_is_string(self): admin_password = main.get_password() self.assertIs(type(admin_password),str) def test_grafana_url_is_string(self): grafana_url = main.get_url() self.assertIs(type(grafana_url),str) def test_grafana_admin_url_is_string(self): admin_url = main.create_url() self.assertIs(type(admin_url),str) if __name__ == '__main__': unittest.main()
A quick note (to future me), each test function should have a name that starts with test_ so that UnitTest runs it.
Once created the tests can be run locally (e.g. without CI) via the command:
python3 -m unittest
And UnitTest will output a full stop (.) for each test that succeeds. If you want to see a more verbose output (e.g. the names of the tests) then add the -v option to command.
Adding The Tests To GitLab CI
The created tests can then be added to GitLab so that they run each time the repository is updated (e.g. each time new code is committed), which allows any program breaking code to be tested and flagged. To add the tests to GitLab a new file at the root of the repository is needed called “.gitlab-ci.yml“. This YAML file contains the instructions telling GitLab how to test the repository code. I’ve gone with a simple file (provided from the GitLab templates) modified to create a virtual environment, install the required Python modules and run the UnitTest command from above.
# This file is a template, and might need editing before it works on your project. # Official language image. Look for the different tagged releases at: # https://hub.docker.com/r/library/python/tags/ image: python:latest # Change pip's cache directory to be inside the project directory since we can # only cache local items. variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/reference/pip_install/#caching # # If you want to also cache the installed packages, you have to install # them in a virtualenv and cache it as well. cache: paths: - .cache/pip - venv/ before_script: - python3 -V # Print out python version for debugging - pip3 install virtualenv - virtualenv venv - source venv/bin/activate - pip3 install requests - pip3 install python-dotenv test: script: - python3 -m unittest
To see the test results / pipeline in action, upload a commit to the project repository, then open the project in GitLab, click CI/CD and click Pipelines. After passing the initial tests I altered my code (knowing it will fail) to test that it does fail in GitLab, and now I’m looking to add to the tests to include mocking of objects.