arrow left
Back to Developer Education

    Automating Tests for Dockerized Django Applications with GitHub Actions

    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:

    1. A good understanding of Django.
    2. Using Git/GitHub.
    3. 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.

    https://sparkling-desk-070a6c243e.media.strapiapp.com/1343_display_blog_6d135405b3.png

    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:

    https://sparkling-desk-070a6c243e.media.strapiapp.com/1343_run_test_53909c88d6.png

    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.

    https://sparkling-desk-070a6c243e.media.strapiapp.com/1343_docker_3056acd351.png

    Setup GitHub Actions

    GitHub Actions enables you to automate, customize specified development, and deployment processes in your GitHub repository.

    Terminologies

    1. Jobs: Step-by-step instructions for action.
    2. 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.

    https://sparkling-desk-070a6c243e.media.strapiapp.com/1343_github_actions_64b37098dc.png

    When you click on it, you should see this:

    https://sparkling-desk-070a6c243e.media.strapiapp.com/1343_github_actions1_5d3bc30fea.png

    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.

    https://sparkling-desk-070a6c243e.media.strapiapp.com/1343_fail_test_c39f2b8b65.png

    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

    Published on: Nov 27, 2021
    Updated on: Jul 15, 2024
    CTA

    Start your journey with Cloudzilla

    With Cloudzilla, apps freely roam across a global cloud with unbeatable simplicity and cost efficiency