Originally posted on my blog
Introduction
Django is a high level framework to build scalable web applications.
Django is used by companies like Google, Facebook, Pinterest, Nasa etc...
Code quality is very important in software engineering.
Testing is one of the best practices of a good developer, the reality is that everyone talk about it but in real word not everyone write tests π
We've many types of tests but in this tutorial i will cover two of them :
- Unit Tests Is a level of software testing where a section(unit) of an application meets the requirements, it focus on one specific function.
- Integration Tests Is a level of software testing where individual sections are combined and tested as a group, it combined multiple pieces of code and functionality.
When and Why run tests
- Testing helps us structure good code, maintain large codebase and find bugs.
- Well written tests make your code - easy to debug, integrate and deploy.
- Well written tests save us lot of times.
- Whatever you're a solo developer or you work in a team you should write tests.
Best practices
- If it can break, it should be tested. This includes models, views, forms, templates, validators, and so forth. Each test should generally only test one function.
- Keep it simple. You do not want to have to write tests on top of other tests.
- Run tests whenever code is PULLed or PUSHed from the repo and in the staging environment before PUSHing to production.
- When upgrading to a newer version of Django:
- upgrade locally,
- run your test suite,
- fix bugs,
- PUSH to the repo and staging, and then
- test again in staging before shipping the code.
Setup
You can find the final source code here
- Create a virtual environment then activate it Learn more
pipenv shell
(phonebook_rest_api-9zIZds3o) ββusername@username-Latitude-7480
- Run migrations
python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contact, contenttypes, sessions
- Run the application
python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
April 21, 2020 - 18:37:31
Django version 3.0.2, using settings 'phonebook.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Open your browser at http://localhost:8000/
We assure that everything is working properly.
Structure
By default all new apps in a Django project come with a tests.py file. Any test within this file that starts with _test__ will be run by Django's test runner. Make sure all test files start with _test__.
As applications grow in complexity, it's recommended to delete this initial tests.py file and replace it with an app-level tests folder that contains individual tests files for each area of functionality.
In real world application you should have something like this :
βββ contact
β βββ admin.py
β βββ api.py
β βββ apps.py
β βββ __init__.py
β βββ migrations
β β βββ 0001_initial.py
β β βββ __init__.py
β βββ models.py
β βββ serializers.py
β βββ tests # new
β β βββ __init__.py
β β βββ test_forms.py # new
β β βββ test_models.py # new
β β βββ test_views.py # new
β βββ urls.py
β βββ views.py
In this article i will keep it simple.
Before writing any tests we will create a new django app and called it pages it will contain our static pages(home and about)
python manage.py startapp pages
Register the app here
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # add this
'contact' # add this
'pages' # new
]
Update pages views.py
# pages views.py
from django.shortcuts import render
from django.views.generic import TemplateView
class HomePageView(TemplateView):
template_name = 'pages/home.html'
class AboutPageView(TemplateView):
template_name = 'pages/about.html'
Create urls.py inside pages
touch pages/urls.py
# pages/urls.py
from django.urls import path
from .views import HomePageView, AboutPageView
urlpatterns = [
path('', HomePageView.as_view(), name='home'),
path('about/', AboutPageView.as_view(), name='about'),
]
And finally update your main urls.py
from django.contrib import admin
from django.urls import path, include # add this
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("contact.urls")), # add this
path("", include("accounts.urls")), # add this
path("", include("pages.urls")) # new
]
Create your templates
mkdir templates/pages
touch templates/pages/home.html
touch templates/pages/about.html
Let's add some html content
<!-- templates/pages/home.html -->
<h1>Homepage</h1>
<!-- templates/pages/about.html -->
<h1>About page</h1>
Run again your server to assure that everything is working properly.
python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
April 22, 2020 - 06:46:31
Django version 3.0.2, using settings 'phonebook.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[22/Apr/2020 06:46:33] "GET / HTTP/1.1" 200 2268
Open your browser then navigate to the homepage urls
Simple TestCase
Now we will test our static pages without involving a database.
Let's test the homepage
# pages/tests.py
from django.http import HttpRequest
from django.test import SimpleTestCase
from django.urls import reverse
from . import views
class HomePageTests(SimpleTestCase):
def test_home_page_status_code(self):
response = self.client.get('/')
self.assertEquals(response.status_code, 200)
def test_view_url_by_name(self):
response = self.client.get(reverse('home'))
self.assertEquals(response.status_code, 200)
def test_view_uses_correct_template(self):
response = self.client.get(reverse('home'))
self.assertEquals(response.status_code, 200)
self.assertTemplateUsed(response, 'pages/home.html')
def test_home_page_contains_correct_html(self):
response = self.client.get('/')
self.assertContains(response, '<h1>Homepage</h1>')
def test_home_page_does_not_contain_incorrect_html(self):
response = self.client.get('/')
self.assertNotContains(
response, 'Hi there! No data found.')
Now run the tests
python manage.py test
System check identified no issues (0 silenced).
.....
---------------------------------------------------------------------------
Ran 5 tests in 0.015s
OK
It's working properly
Let's do the same thing for the about page
# pages/tests.py
class AboutPageTests(SimpleTestCase):
def test_about_page_status_code(self):
response = self.client.get('/about/')
self.assertEquals(response.status_code, 200)
def test_view_url_by_name(self):
response = self.client.get(reverse('about'))
self.assertEquals(response.status_code, 200)
def test_view_uses_correct_template(self):
response = self.client.get(reverse('about'))
self.assertEquals(response.status_code, 200)
self.assertTemplateUsed(response, 'pages/about.html')
def test_about_page_contains_correct_html(self):
response = self.client.get('/about/')
self.assertContains(response, '<h1>About page</h1>')
def test_about_page_does_not_contain_incorrect_html(self):
response = self.client.get('/')
self.assertNotContains(
response, 'Hi there! I should not be on the page.')
Then run
python manage.py test
System check identified no issues (0 silenced).
..........
---------------------------------------------------------------------------
Ran 10 tests in 0.023s
OK
This works fine
TestCase
Testcase is the most common class to use for writing test in Django it gives us the ability to mock queries to the database.
Let's test out our Contact database model
# contact/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Contact
class ContactTests(TestCase):
def setUp(self):
Contact.objects.create(
first_name='Ousseynou',
last_name="Diop",
phone="779929900",
email="hello@me.com"
)
def test_email_content(self):
contact = Contact.objects.get(id=1)
expected_object_name = f'{contact.email}'
self.assertEquals(expected_object_name, 'hello@me.com')
def test_contact_list_view(self):
response = self.client.get(reverse('contacts'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'hello@me.com')
self.assertTemplateUsed(response, 'contact/contact_list.html')
Then run
python manage.py test
System check identified no issues (0 silenced).
..........
---------------------------------------------------------------------------
Ran 10 tests in 0.023s
OK
This works fine! let's break the code.
# contact/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Contact
class ContactTests(TestCase):
def setUp(self):
Contact.objects.create(
first_name='Ousseynou',
last_name="Diop",
phone="779929900",
email="hello@me.com"
)
def test_email_content(self):
contact = Contact.objects.get(id=1)
expected_object_name = f'{contact.email}'
self.assertEquals(expected_object_name, 'hello@gmail.com') # new
def test_contact_list_view(self):
response = self.client.get(reverse('contacts'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'hello@me.com')
self.assertTemplateUsed(response, 'contact/contact_list.html')
Then run the test again
python manage.py test
System check identified no issues (0 silenced).
.F..........
======================================================================
FAIL: test_email_content (contact.tests.ContactTests)
---------------------------------------------------------------------------
Traceback (most recent call last):
File "/home/username/projects/phonebook_rest_api/contact/tests.py", line 20, in test_email_content
self.assertEquals(expected_object_name, 'hello@gmail.com')
AssertionError: 'hello@me.com' != 'hello@gmail.com'
- hello@me.com
? ^
+ hello@gmail.com
? + ^^^
---------------------------------------------------------------------------
Ran 12 tests in 0.030s
FAILED (failures=1)
Destroying test database for alias 'default'...
Our tests are failed.
Now you understand how tests are important in software engineering.
Conclusion
In this article we've learned how to write tests for our django project.
The next steps are to write TDD, setup CI/CD in order to maintain code quality.
Thank you for reading π See you in the next article.
Top comments (6)
Nice article!
Welcome Ibrahima
Nice, Thanks for writing!
Welcome Sm0ke
For django apps I usually choose to write unit tests with pytest, pytest-django & pytest-cov :)
Example output.
Thanks !
They are great tools.
This is some advance testing tools i will cover them next...