Why testing is important ?
Testing is very important to any web appication. Because the application may contain errors that are not inteded and it may cause some serious problems. So, we need to test everything to make sure that it’s working as intended.
Types of Testing
In general testing can be done in two ways 1. Manual testing 2. Automated Testing
-
Manual Testing:
It needs a human resource to test the application and we call the resource as Tester. Tester will test the appication manually by checking each and every functionality that the web appication provides. Manual testing is a time cosuming process so it will take more time and some times there is possibility of missing some functionality to test. This will result in bugs. So, manual testing is not accurate.
-
Automated Testing:
In this approach we automate tests for each and every functionality of the web appication. This approach is 100% accurate and it takes less time to test the web appication functionalities and we don’t need a human intervention.
Unit Testing
- Let’s consider a large web applications like Amazon, Flipcart, etc. These are very complex applications. Testing the application as whole is difficult task and it may not yield best results. So, we divide the application into simpler modules and modules to sub modules and so on. We call the sub module as Unit and it will not further devided. We test these units individually by writing the automated tests for each so, it will produce accurate test results.
Django Unit Testing Process
Each django unit test comprises of 3 methods. 1. setUp
, 2. test_<test_name>
and 3.tearDown
.
When we run the test using command python manage.py test
test runner will create a empty test database with required migrations. It will look for the tests in the files whose name starts with test and finds the all tests. The test runner will take the each test case and runs it. Whenever the test is running first it will call “setUp” method which is used to create basic requirements to run the test. After, it will run the test and then the method “tearDown
” will be called. “tearDown
” is used to destroy the data created by the “setUp
” method. Django unit testing has a automated mechanism to remove it. But, in some cases we need to do it manually.
Note: Take separate class for each test. Writing multiple tests in the same class will slow down the tests run time.
Advantages of writing unit tests in Django
Django is comprised of different modules like models, forms, views, templates, etc. When we develop an application we devide it into functionalities and develop them independently. So, we write unit test to test the funcitonality whether its working as we expected or not. Ofcourse, it will work as we expected. We develop all other functionalities so, apperently the applicaiton will become complex. In development process we definitely have a dependent funcitonalities. After completion of the application we make it available to the world. As we follow agile development the changes in the functionalities are infly. So, changing in one functionality will result error in other functionality. In such cases the unit test’s helps us to fix the bugs that produced by latest changes. So, it’s vital to write unit tests in our django applications.
Best practices for Django Unit Testing
- Each test case should have to test a single functionality
- We have to keep in simple and there should not be a dependency between testcases
- Run the
tests
every time you pull the code or before pushing the code. - we can use the naming convension
tests.py
or we can create directory namedtests
like below for better project structure.└── app_name └── tests ├── __init__.py ├── test_forms.py ├── test_models.py └── test_views.py
Modules needs to be tested in Django
Let’s see example test cases how we can write for modules like models, forms, views, template tags, etc.
-
Writing tests for Models
models.py
from django.db import models
from django.contrib.auth.models import AbscractUser
class User(AbstractUser):
AUTHOR = 1
PUBLISHER = 2
USER_TYPES = (
(AUTHOR, 'Author'),
(PUBLISHER, 'Publisher'),
)
user_type = models.CharField(choices=USER_TYPES)
no_of_books = models.IntegerField(default=0)
@classmethod
def get_authors(cls,):
return cls.objects.filter(uset_type=cls.AUTHOR)
def can_write_books(self):
return self.user_type == self.AUTHOR
tests.py
from django.test import TestCase
from .models import User
class UserTestCase(TestCase):
def setUp(self):
self.author = User.objects.create(
username='[email protected]',
email='[email protected]',
user_type=User.AUTHOR
)
self.publisher = User.objects.create(
username='[email protected]',
email='[email protected]',
user_type=User.AUTHOR
)
def test_get_authors(self):
self.assertEqual(User.get_authors(), 1)
def test_can_write_books(self):
self.assertTrue(self.author.can_write_books())
self.assertFalse(self.publisher.can_write_books())
-
Writing tests for Forms
forms.py
from django import forms
class RegistrationForm(forms.Form):
username = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput())
confirm = forms.CharField(widget=forms.PasswordInput())
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
confirm = self.cleaned_data.get('confirm')
if confirm != password:
raise forms.ValidationError({
'confirm': 'Passwords mismatched'
})
return self.cleaned_data
tests.py
from django.test import TestCase
from .forms import RegistrationForm
class TestRegistrationForm(TestCase):
def test_registration_form(self):
# test invalid data
invalid_data = {
"username": "[email protected]",
"password": "secret",
"confirm": "not secret"
}
form = RegistrationForm(data=invalid_data)
form.is_valid()
self.assertTrue(form.errors)
# test valid data
valid_data = {
"username": "[email protected]",
"password": "secret",
"confirm": "secret"
}
form = RegistrationForm(data=valid_data)
form.is_valid()
self.assertFalse(form.errors)
-
Writing tests for Views
views.py
from django.views.generic import FormView
from django.http import JsonResponse
from .forms import RegistrationForm
class UserRegistrationView(FormView):
form_class = RegistrationForm
def form_valid(self, form):
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
User.objects.create_user(username, password=password)
res_data = {
'error': False,
'message': 'Success, Please login'
}
return JsonResponse(res_data)
def form_invalid(self, form):
res_data = {
'error': True,
'errors': form.errors
}
return JsonResponse(res_data)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('registration/',
views.UserRegistrationView.as_view(), name='register'
)
]
tests.py
from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.core.urlresolvers import reverse
class TestUserRegistrationView(TestCase):
def setUp(self):
self.client = Client()
def test_registration(self):
url = reverse('register')
# test req method GET
response = self.client.get(url)
self.assertEqual(response.status, 200)
# test req method POST with empty data
response = self.client.post(url, {})
self.assertEqual(response.status, 200)
exp_data = {
'error': True,
'errors': {
'username': 'This field is required',
'password': 'This field is required',
'confirm': 'This field is required',
}
}
self.asssertEqual(exp_data, response.json())
# test req method POST with invalid data
req_data = {
'username': '[email protected]',
'password': 'secret',
'confirm': 'secret1',
}
response = self.client.post(url, req_data)
self.assertEqual(response.status, 200)
exp_data = {
'error': True,
'errors': {
'confirm': 'Passwords mismatched'
}
}
self.asssertEqual(exp_data, response.json())
# test req method POST with valid data
req_data = {
'username': '[email protected]',
'password': 'secret',
'confirm': 'secret',
}
response = self.client.post(url, req_data)
self.assertEqual(response.status, 200)
exp_data = {
'error': False,
'message': 'Success, Please login'
}
self.asssertEqual(exp_data, response.json())
self.assertEqual(User.objects.count(), 1)
-
Writing tests for Template tags
See how to write custom template tags in django official docs
custom_template_tags.py
from django import template
register = template.Library()
@register.filter(is_safe=True)
def add_xx(value):
return '%sxx' % value
tests.py
from django.test import TestCase
from django.template import Template
class TestAddXX(TestCase):
def test_add_xx(self):
template = Template('''
{% load custom_template_tags %}
{{ "django_2."|add_xx }}
''')
rendered_text = template.render()
self.assertIn("django_2.xx", rendered_text)
Finding code coverage with “coverage”
Now, it’s time to find how much code is executing when test cases are running. If we have written all possible tests for the application then the code coverage will be 100% otherwise it will show us the missing lines of code. To do it we have to install the pip package “coverage”
Install coverage using pip
pip install coverage
Using coverage package
coverage run --source=app1,app2 ---omit=*/migrations/* manage.py test
Above command will executes the tests and stores the test results.
To know the results run the below command
coverage report -m
Above command will return results like below(example output).
Name Stmts Miss Cover Missing
-------------------------------------------------------
app1/views.py 20 4 80% 33-35, 39
app1/models.py 20 4 80% 35-41, 57
app1/templatetags/ctags.py 56 6 89% 17-23
-------------------------------------------------------
TOTAL 76 10 87%
References:
http://django-testing-docs.readthedocs.io/en/latest/basic_doctests.html https://docs.djangoproject.com/en/2.0/topics/testing/overview/
Thank you for reading the Agiliq blog. This article was written by Anjaneyulu Batta on May 3, 2018 in python , django , unit-testing .
You can subscribe ⚛ to our blog.
We love building amazing apps for web and mobile for our clients. If you are looking for development help, contact us today ✉.
Would you like to download 10+ free Django and Python books? Get them here