Adventures in Machine Learning

Mastering Test Driven Development: Setting up and Writing Tests for a Django Project

Test Driven Development (TDD) is an industry standard for software development that is geared towards automated testing and code reliability. In simplistic terms, it is a software development cycle where developers write automated tests first and then write the code to pass those tests.

The benefits of TDD are numerous and include breaking down complex problems, better understanding of code, eliminating errors in code, and reducing the time developers need to spend on manual testing. In this article, we will explore the basics of TDD and how to set up the environment for writing the first functional test.

Setting up the test environment

Before starting with TDD, you need to set up your test environment. You will need to create a virtual environment (virtualenv) to install the dependencies without messing with your global system installation.

The virtualenv package allows you to create an isolated Python environment that can have its set of dependencies installed without affecting the host machine’s global Python environment.

Once you have created an isolated Python environment, you will need to set up the directory structure for your project.

This can be done by creating a Django project using the following command:

django-admin startproject myproject

This command will create a new directory called myproject, which will contain the necessary files for your project.

Writing the first functional test

The first functional test is the foundation for the rest of your project. A functional test is usually an end-to-end test that mimics the actions of a user.

In our case, we will write a functional test that checks if the Django homepage is working correctly.

To start, we will use a Python library called Selenium to emulate a web browser’s actions.

Selenium is a popular tool for writing tests for web applications and websites.

from selenium import webdriver
import unittest

class NewVisitorTest(unittest.TestCase):

    def setUp(self):
        self.browser = webdriver.Firefox()

    def tearDown(self):
        self.browser.quit()

    def test_can_access_homepage(self):
        self.browser.get('http://localhost:8000')
        self.assertIn('Welcome to Django', self.browser.title)

The above code opens a Firefox browser and navigates to the URL http://localhost:8000. It then asserts that the word ‘Welcome to Django’ is present in the browser’s title.

Writing the code to make the test pass

Now that we have written our first functional test, we need to write the code to make it pass. We will create a view in Django that renders the homepage.

Open myproject/settings.py and add 'django.contrib.staticfiles' to the INSTALLED_APPS list.

Next, create a new file called home/views.py in your project directory and add the following code:

from django.shortcuts import render

def home(request):
    return render(request, 'home.html')

The home function takes in a request object and returns the rendered home.html template.

We will now create a new file called home.html in the templates directory we created earlier and add the following code:



    
        Welcome to Django
    
    
        

Welcome to Django

The home.html template contains HTML markup that will be displayed when users visit the Django homepage.

Finally, we need to create an endpoint in myproject/urls.py that maps the homepage URL to our home view:

from django.urls import path
from home.views import home

urlpatterns = [
    path('', home, name='home'),
]

Conclusion

In conclusion, Test Driven Development is a software development cycle that aims to automate the testing process and reduce the time spent on manual testing. The benefits of TDD are numerous, including better understanding of code, breaking down complex problems, and reducing errors in code.

To start with TDD, you need to set up your test environment and create your first functional test using Selenium and Django. Finally, you need to write the code to make the test pass, which involves creating a view, template, and URL endpoint.

With these tools, you are well on your way to becoming a proficient TDD developer. Version Control is an essential aspect of software development that allows developers to keep track of their code changes and collaborate effectively with others.

One popular version control system used by developers is Git. In this article, we will explore how to set up Git and .gitignore file, refactor the functional test to use LiveServerTestCase, test admin login functionality, and dive deeper into functional testing by exploring the differences between functional and unit tests.

Setting up Git and .gitignore file

Before you start writing code, it is critical to set up a Git repository and .gitignore file for your project. A Git repository acts as a central storage location where anyone can contribute and retrieve code.

To create a new Git repository, open the command line and navigate to the project directory. Next, initialize a Git repository using the following command:

git init

This command will create a new Git repository in your project directory. A .gitignore file is crucial in hiding sensitive data from the Git repository and for ignoring files that do not need to be tracked.

For instance, files created by the operating system, files created by code editors, and temporary files.

Create a new file called .gitignore in your project directory and add the following code:

db.sqlite3
__pycache__/
*.pyc
*.pyo
*.env
*.log
*.swp
*.swo
/static 
/media

This code will prevent Git from tracking files with the extensions .env, .log, .swp, .swo, .pyc, and .pyo.

It will also ignore the directories named __pycache__, /static and /media.

Refactoring the functional test to use LiveServerTestCase

In the previous section, we wrote the first functional test using Selenium. The Selenium test is great for testing end-to-end functionality but is not ideal for testing views and templates in Django.

The solution lies in Django’s LiveServerTestCase. It uses a live development server to test Django views in real-time and does not rely on an external testing tool like Selenium.

To start writing tests, first, import LiveServerTestCase from the django.test module.

from django.test import LiveServerTestCase

class NewVisitorTest(LiveServerTestCase):
    ...

Replace the unittest.TestCase with LiveServerTestCase. With LiveServerTestCase, we can write tests that generate URL requests and test the responses received from views.

Testing admin login functionality

Django comes with built-in features for creating an admin user who has advanced capabilities like managing and moderating content. In functional testing, it’s essential to test the user stories around admin usage.

We can start by writing tests that check the admin login functionality. In Django, an admin user can log in by going to the URL /admin.

To write tests, create a new functional test that starts with logging in as an admin user.

from django.contrib.auth.models import User

class AdminLoginTest(LiveServerTestCase):

    def setUp(self):
        User.objects.create_superuser(username='admin',
                                      password='password',
                                      email='[email protected]')

    def test_admin_login(self):
        self.browser.get(self.live_server_url + '/admin/')
        self.assertIn('Log in', self.browser.title)
        # Log in as an admin user.

        login_username_input = self.browser.find_element_by_id('id_username')
        login_username_input.send_keys('admin')
        login_password_input = self.browser.find_element_by_id('id_password')
        login_password_input.send_keys('password')
        login_button = self.browser.find_element_by_xpath(
            '//input[@type="submit" and @value="Log in"]'
        )
        login_button.click()
        self.assertIn('Site administration', self.browser.title)

In the above code, we create a superuser with credentials admin/password and log in using these credentials. We check if the title of the resulting admin page is ‘Site administration,’ which confirms that the user has logged in successfully.

Overview of functional vs. unit tests

Functional tests differ from unit tests because they focus on testing the external behavior of software functionalities, whereas unit tests focus on testing individual components of code.

A functional test typically tests the entire application from a user’s perspective by simulating how the user would interact with the product. Functional tests are also known as behavior tests or feature tests because they primarily focus on testing an application’s feature set.

In contrast, unit tests focus on testing the developer’s perspective of code components.

Restructuring the testing environment

Typically, in Django, tests are placed in the tests.py file in each app’s directory. This approach works fine for small projects, but it can be cumbersome for larger projects.

It becomes challenging to manage the codebase when multiple tests are written in a single file. The solution to this is to split the tests into separate files organized into a tests directory.

Create a directory called tests in each app’s directory to hold the tests. Place each test module in a file within this directory.

Adding the initial test for the Contacts app

We can build on the functional test knowledge we have gained so far by testing specific apps outside of the core Django project. For example, we can write tests for the Contacts app.

The Contacts app is an application that enables users to store and manage their contact lists. Start by installing django_extensions.

pip install django_extensions

Add it to your installed apps in the settings.py.

INSTALLED_APPS = [
    # Third-party apps.

    'django_extensions',
    # Local apps.     'home',
    'contacts',
]

Next, create an initial test for the Contacts app that ensures the required views are available.

from django.urls import reverse, resolve
from django.test import TestCase
from contacts.views import MyContactsList

class ContactsAppTestCase(TestCase):

    def setUp(self):
        self.url = reverse('contacts_list')
        self.view = MyContactsList.as_view()

    def test_contacts_url_resolves_to_contacts_view(self):
        match = resolve(self.url)
        self.assertEqual(match.func.view_class, MyContactsList)

    def test_contacts_view_success_status_code(self):
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)

    def test_contacts_view_template_file_existence(self):
        response = self.client.get(self.url)
        self.assertTemplateUsed(response, 'contacts_list.html')

Conclusion

In conclusion, setting up Git and .gitignore file are essential first steps in software development. Refactoring functional tests to use LiveServerTestCase is an ideal way to test views and templates in Django.

Admin login functionality tests are crucial because they test user stories and ensure that the admin user can perform their duties. Understanding the differences between functional and unit tests is essential, and restructuring the testing environment is necessary to keep the codebase organized.

Finally, we can test specific apps outside the core Django project by writing initial tests for the Contacts app. Setting up a new app in a Django project involves several stages, including configuring the app, deploying it, and setting up the admin interface.

In this article, we will explore how to set up and configure the Contacts app, add it to the project, and set up the admin interface for it. We will also delve into unit testing, which is the process of testing individual components of code to ensure they behave as expected.

Writing code to pass the initial Contacts app test

Before adding the Contacts app to the project, we need to write code that passes the initial test created in the previous section. We will start by creating a new Django app called contacts.

python manage.py startapp contacts

This command will create a new directory called contacts in your project directory. The startapp command automatically creates all the necessary files and directories for the app.

We can now write code to pass the initial tests for the Contacts app. In our contacts views.py file, we will create a new view called MyContactsList.

from django.views.generic import ListView
from .models import Contact

class MyContactsList(ListView):
    template_name = 'contacts_list.html'
    model = Contact

Adding the Contacts app to the project

To add the Contacts app to the project, we need to add the app name to the INSTALLED_APPS list in our settings.py file.

INSTALLED_APPS = [
    ...

    'contacts',
    ... ]

We can now run the makemigrations and migrate commands in our project directory to create the necessary database tables for the Contacts app.

python manage.py makemigrations contacts
python manage.py migrate

Setting up the admin interface for the Contacts app

Enabling the admin interface for the Contacts app requires registering its models with admin.site. In the admin.py file of the Contacts app, we will import the models and add them to admin.site.register().

from django.contrib import admin
from .models import Contact

admin.site.register(Contact)

We can now access the Contacts app in the admin interface by navigating to /admin, where we will see a link to the Contacts app. Clicking on it will take us to a page that allows us to add, edit, or delete contacts.

Overview of unit tests

Unit tests test individual components of code and the function and behavior of specific code blocks, whereas functional tests test the external behavior of software functionalities. In Django, we can use unit tests to test specific views, models, forms, and other code components in our app.

Setting up unit tests for the Views in the Contacts app

To test views in the Contacts app, we will import the TestCase module from Django’s django.test package. We will create a new ContactViewTest class that inherits from TestCase.

from django.test import TestCase
from django.urls import reverse
from .models import Contact

class ContactViewTest(TestCase):

    def setUp(self):
        self.contact = Contact.objects.create(
            first_name='John',
            last_name='Doe',
            phone_number='1234567890',
            email='[email protected]',
            address='123 Main St'
        )

    def tearDown(self):
        self.contact.delete()

    def test_main_view(self):
        response = self.client.get(reverse('contacts_list'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'John')

    def test_all_contacts_view(self):
        response = self.client.get(reverse('all_contacts'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'John')

    def test_add_contact_view(self):
        response = self.client.post(reverse('add_contact'), {
            'first_name': 'Jane',
            'last_name': 'Doe',
            'phone_number': '5555555555',
            'email': '[email protected]',
            'address': '456 Second St'
        })
        self.assertEqual(response.status_code, 302)
        self.assertEqual(Contact.objects.filter(first_name='Jane').count(), 1)

The setUp() method creates a new contact for us to use in testing. The tearDown() method deletes the contact after testing the code.

Writing unit tests for main view, all contacts view, and add contact view

In the ContactViewTest class, we create several methods that test specific views in the Contacts app. The test_main_view() method tests that the contacts_list.html template is rendered successfully and that it displays the previously created contact.

The test_all_contacts_view() method tests that the all_contacts.html template is rendered successfully and that it displays the previously created contact. The test_add_contact_view() method tests that a new contact is added to the database successfully when we submit a POST request to the add_contact.html template.

In conclusion, setting up the Contacts app in a Django project involves several steps, including creating the app, configuring it, deploying it, and setting up the admin interface. Writing unit tests ensure that individual components of code behave as expected, whereas functional tests test the external behavior of software features.

Popular Posts