Automating Tests for Dockerized Django Applications with GitHub Actions
In this article, the reader will learn how to test a Django application with Pytest, dockerize it, and configure GitHub Actions to automate your Django tests. <!--more--> Pytest is a Python library used for running tests for Python code. GitHub Actions enables you can automate much repetitive tasks in your repository.
Docker ensures your application works the same way on all platforms. Adding these functionalities to your application can make it easier for continuous delivery and collaboration.
Prerequisites
The reader should have the following to follow along with this tutorial:
- A good understanding of Django.
- Using Git/GitHub.
- Docker and Docker compose installed.
Building a base Django app
Let us build the project that we will be using for this tutorial. We will create a new virtual environment for the project, which will come in handy when trying to get a list of all the dependencies.
After creating the virtual environment, install Django with the following command executed in the terminal.
(env)$ pip install django
Run the command below to create a new project:
(env)$ django-admin startproject <name_of_project>
For instance, if the name of your application is django_test_githubactions
, use that name in the command.
(env)$ django-admin startproject django_test_githubactions
cd
into the directory that contains manage.py and execute the command below to initialize the Django application:
(env)$ python manage.py startapp blog
Add blog
application to the list of the installed application in the settings.py
file. This step enables Django to recognize it as a sub-application to the main application.
INSTALLED_APPS = [
"blog", #new
]
We will be building a blog application for this demostration, so in the *
blog/models.py` file and paste the code snippet below:
from django.db import models
class Article(models.Model):
author_name = models.CharField(max_length=30)
title = models.CharField(max_length=20)
content = models.CharField(max_length=200)
def __str__(self):
return self.title
Next, we have to write the logic to display the content. To do this, navigate to app/views.py
. Then, add the code snippet below in that file.
from django.shortcuts import get_object_or_404, render
from .models import Article
def content_view(request, pk):
post = get_object_or_404(Article, pk=pk)
return render(request, "blog/article.html", {'post':post})
Next, update your urls.py
file with the code below to access that route in the browser.
from blog import views
urlpatterns = [
path('<int:pk>', views.content_view, name="content")
]
Next, create a templates
folder in the app directory to hold the template to display the blog content.
Navigate to your app folder and create templates/blog/article.html
and paste the code below:
Blog title:
{% if post.content %}
{{ post.content }}
{% endif %}
Migrate the models
to start adding articles to the database. To do this, go to your terminal/command prompt and run:
(env)$ python manage.py makemigrations
Then run:
(env)$ python manage.py migrate
Populate the database
We need to populate our database to know whether our application is working as expected. To populate the database, go to your terminal and type the instructions below to go into the Django shell:
(env)$ python manage.py shell
Then run the code below one after the other; the second line adds a single record to the database and saves it. You can use the same format to add more data.
>>> from blog.models import Article
>>> Article.objects.create(author_name="Ali",title="First app", content="Some detail of the article").save()`
Now run your server and go to your localhost(http://127.0.0.1:8000/) then add the id of the article you just added, like this: http://127.0.0.1:8000/1, you will see that the article content is being displayed.
How to add tests to Django application
This section will show you how to add tests to your URLs and models. Before testing the application, execute the command below to install the testing library.
(env)$ pip install pytest pytest-django
Pytest-django is a plugin built to make it easier for you to use Pytest with Django.
Next, create a file with the name pytest.ini
at the root of your project. In the pytest.ini
file, you will put the path to your project's settings.py
file.
In the pytest.ini
file, paste the code below:
[pytest]
DJANGO_SETTINGS_MODULE = django_test_githubactions.settings
Next, create a test folder in the app directory where all your test files for that app will be stored. You will create a new directory in the blog application, blog/tests/
.
To start, we will test the urls.py
file, so you will create a new file in the test folder blog/tests/test_urls.py
.
All test files must begin with
test_
because that is the convention used with Pytest.
Paste the code below in the test_urls.py
file you just created.
from django.urls import reverse, resolve
from django.urls import path
class TestUrls:
# here, you are checking if the path's view name is content
def test_post_content_url(self):
path = reverse('content', kwargs={'pk':1})
assert resolve(path).view_name == "content" # here you are checking if the path's view name is content
Next, we will be testing the models, create a new file for it app/tests/test_models.py
, and add the code snippet below:
import pytest
from app.models import Article
@pytest.mark.django_db
def test_article_create():
# Create dummy data
article = Article.objects.create(
author_name="Muhammed Ali",
title="Simple article",
content="This is my content",
)
# Assert the dummy data saved as expected
assert article.author_name=="Muhammed Ali"
assert article.title=="Simple article"
assert article.content=="This is my content"
To run the tests, go to your command line and run:
(env)$ python -m pytest
You should see a similar process like the image below to show that all the tests passed:
Setting up Docker
This section helps you learn how to use Docker and Docker compose to run your application. Assuming you have installed Docker and Docker compose on your local machine, create a Dockerfile
file at the root of your project. In the Dockerfile
, you will put step-by-step instructions that will be used to build the Docker image.
The Dockerfile
you just created should contain the code below:
FROM python:3.7-alpine
ENV PYTHONUNBUFFERED 1
COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
# make a directory in our Docker image in which we can use to store our source code
# Copy the project folder from our local machine to the docker image
RUN mkdir /project
WORKDIR /project
COPY . .
FROM python:3.7-alpine
is the image you will inherit your Dockerfile from. It is usually the language you are using.
ENV PYTHONUNBUFFERED 1
is necessary, so Docker does not buffer the output and that you can see the output of your application (e.g., Django logs) in real-time.
COPY ./requirements.txt /requirements.txt
copies the requirements.txt file adjacent to the Dockerfile file in your local machine for our Docker image requirements.txt.
RUN pip install -r /requirements.txt
installs requirements.txt file in the docker image.
Next, create a requirements.txt
file at the root of your project. This file will contain a list of all the dependencies we would like to install for the project.
Paste the text below in the requirements.txt
file.
asgiref==3.4.1
attrs==21.2.0
Django==3.2.7
pytest==6.2.5
pytest-django==4.4.0
python-dateutil==2.8.2
pytz==2021.1
six==1.16.0
sqlparse==0.4.1
Setup Docker Compose
In this section, you will write the instruction to tell Docker how to run your application. First, at the root of your project, create a docker-compose.yml
file and add the lines of code.
version: "3" # Verion of docker-compose we want to use
services:
proj:
build:
# command that is used to run services
context: . # builds the current directory
# map port on local machine to port on the docker image
ports:
- "8000:8000"
volumes:
- .:/project # Updates the image with new changes in the code
command: >
sh -c "python manage.py runserver 0.0.0.0:8000"
After that, go to your terminal and run $ docker-compose build
to build our image using the docker-compose configuration.
Docker-compose runs at 0.0.0.0:8000
which Django does not recognize, so you need to add it to your ALLOWED_HOSTS
in your settings.py
file, like this:
`ALLOWED_HOSTS = ['0.0.0.0']`
Now run $ docker-compose up
to start the server and go to http://0.0.0.0:8000/1 on your browser, and you will see the blog you created.
Setup GitHub Actions
GitHub Actions enables you to automate, customize specified development, and deployment processes in your GitHub repository.
Terminologies
- Jobs: Step-by-step instructions for action.
- Workflows: A workflow is a list of automated processes of one or more jobs configured on a YAML file.
Activate GitHub Actions in your Project
On your text editor, navigate to the root of your project and create a directory named .github/workflows
in there, create a file named main.yml
f. The main.yml
file will contain all the GitHub Actions commands.
Paste the code below in the main.yml
file you just created.
name: test_project
on:
# activates the workflow when there is a push or pull request on the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test_project:
# the operating system your job will run on
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install -r requirements.txt # install requirements to enable GitHub run our code
- run: pytest . # run the unit test
The code above just runs tests on your latest push. You can visit the GitHub docs page to learn more about syntax.
GitHub Actions at work
Commit and push all the code you have at the moment to GitHub. After pushing your code, go to your project on GitHub and click on the “Actions” tab. If you followed the steps correctly, you should see that your GitHub Actions ran completely and that your application has been tested and all tests passed.
When you click on it, you should see this:
To check if the intention of GitHub Actions is met, you will update the test, so it fails.
In the models' test, change it to:
def test_article_create():
article = Article.objects.create(
author_name="Muhammed Ali",
title="Simple article",
content="The article's content",
)
assert article.author_name=="Muhammed Al" # this will make sure the test fails
assert article.title=="Simple article"
assert article.content=="The article's content"
Commit and push your code. Then, go to your project Actions, and you should see the test fails to show that our GitHub Actions instructions are working as expected.
Conclusion
This tutorial taught you to write unit tests for Django application URLs and models. At the same time, you could automate the process of running the tests when your application is pushed to GitHub.
In addition, you can add some linting tests to the workflow to improve the continuous integration of your application.
Hopefully, you can add some continuous delivery with GitHub Actions for your future projects with this article.
The code for this tutorial can be found on GitHub.
Happy coding!
Peer Review Contributions by: Jerim Kaura