Django - TEST FIRST
Este post irá conter uma “coletânea” de formas de testar seu código Django. Ele não será um passo a passo de como fazer TDD ou testes de regressão. Mas um conjunto de “blocos de código” para servir de inspiração na hora de desenhar seus testes.
OBSERVAÇÃO IMPORTANTE: Todas as informações nesse post foram retiradas através das interações ocorridas no curso Welcome to the Django, ministrado pelo Henrique Bastos.
Um teste básico
Basicamente um caso de teste é composto por um método de “set up”, alguns testes, e um método de “rollback”. O setUp e o tearDown não são obrigatórios e só devem ser utilizados se forem realmente necessários.
from django.test import TestCase
class UseCaseTest(TestCase):
def setUp(self):
# organize variáveis e chamadas padrões aqui
# self.response = ...
# self.obj = ...
def test_some_func(self):
# Primeiro teste do caso de testes
def test_some_other_stuff(self):
# Segundo teste
def tearDown(self):
# Lógica posterior à execução dos testes
# rollback...
Testando uma view (GET)
Exemplo de testes para um GET em uma view:
from django.test import TestCase
from django.shortcuts import resolve_url as r
from path.to.models import User
from path.to.forms import SubscriptionForm
class ViewUseCaseTest(TestCase):
def setUp(self):
self.obj = User.objects.create(dict())
self.response = self.client.get(r('home'))
self.response_with_object = self.client.get(r('detail', obj))
# Normalmente esses dois "gets" estariam em dois casos de teste diferentes
# Estão juntos aqui somente para exemplificação
def test_status_code(self):
self.assertEqual(200, self.response.status_code)
def test_template_used(self):
self.assertTemplateUsed(self.response, 'path/to/template.html')
def test_context(self):
user = self.response.context['user']
self.assertTrue(user)
self.assertIsInstance(user, User)
def test_html_content(self):
contents = ['Severo', '<a href=', 'Título da página']
for content in contents:
with self.subTest():
self.assertContains(self.response, content)
contents = [
(1, 'Título da página'),
(5, '<a href='),
]
for count, content in contents:
with self.subTest():
self.assertContains(self.response, content, count)
def test_not_found(self):
response = self.client.get(r('detail', 0))
self.assertEqual(404, response.status_code)
def test_csrf(self):
""" HTML must contain csrf"""
self.assertContains(self.response, 'csrfmiddlewaretoken')
def test_has_form(self):
""" Context must have subscription form"""
form = self.response.context['form']
self.assertIsInstance(form, SubscriptionForm)
def test_subscription_link(self):
expected = 'href="{}"'.format(r('subscriptions:new'))
self.assertContains(self.response, expected)
Testando uma view (POST)
Exemplos de teste para um POST em uma view:
from django.test import TestCase
from django.shortcuts import resolve_url as r
from django.core import mail
from path.to.models import Subscription
class PostValidUseCaseTest(TestCase):
def setUp(self):
data = dict()
self.response = self.client.post(r('subscriptions:new'), data)
def test_post(self):
"""Valid POST should redirect to /subscriptions/1/ """
self.assertRedirects(self.response, r('subscriptions:detail', 1))
def test_subscribe_email(self):
"""Valid POST should send ONE email"""
self.assertEqual(1, len(mail.outbox))
def test_save_subscription(self):
"""Valid POST should create an object"""
self.assertTrue(Subscription.objects.exists())
class PostInvalidUseCaseTest(TestCase):
def setUp(self):
self.response = self.client.post(r('subscriptions:new'), {})
def test_post(self):
"""Invalid POST should not redirect"""
self.assertEqual(200, self.response.status_code)
def test_template(self):
"""Invalid POST should have same template"""
self.assertTemplateUsed(self.response, 'subscriptions/subscription_form.html')
def test_has_form(self):
"""Invalid POST must have a form"""
form = self.response.context['form']
self.assertIsInstance(form, SubscriptionForm)
def test_has_errors(self):
"""Invalid POST must have a form with errors"""
form = self.response.context['form']
self.assertTrue(form.errors)
def test_dont_save_subscription(self):
"""Invalid POST can't create and object"""
self.assertFalse(Subscription.objects.exists())
def test_template_has_non_field_errors(self):
invalid_data = dict()
response = self.client.post(r('subscriptions:new'), invalid_data)
self.assertContains(response, '<ul class="errorlist nonfield">')
Testando envio de email
Exemplos de teste para envio de email:
from django.core import mail
from django.test import TestCase
from django.shortcuts import resolve_url as r
class EmailUseCaseTest(TestCase):
def setUp(self):
data = dict()
self.client.post(r('subscriptions:new'), data)
self.email = mail.outbox[0]
def test_subscription_email_subject(self):
expect = 'Confirmação de inscrição'
self.assertEqual(expect, self.email.subject)
def test_subscription_email_from(self):
expect = 'email@example.com.br'
self.assertEqual(expect, self.email.from_email)
def test_subscription_email_to(self):
expect = ['email@example.com.br', 'example@email.com']
self.assertEqual(expect, self.email.to)
def test_subscription_email_body(self):
contents = []
for content in contents:
with self.subTest():
self.assertIn(content, self.email.body)
Testando models
Exemplos de teste para models, managers, etc:
from datetime import datetime
from django.shortcuts import resolve_url as r
from django.test import TestCase
from path.to.models import Subscription, Contact, Speaker
from path.to.managers import ContactManager
class SubscriptionModelTest(TestCase):
def setUp(self):
self.obj = Subscription(
name="Fabrício Severo"
)
self.obj.save()
def test_create(self):
self.assertTrue(Subscription.objects.exists())
def test_created_at(self):
"""Subscription mus have an auto created_at attr."""
self.assertIsInstance(self.obj.created_at, datetime)
def test_str(self):
self.assertEqual('Fabrício Severo', str(self.obj))
def test_some_default_value(self):
"""By default, paid must be false"""
self.assertEqual(False, self.obj.paid)
def test_absolute_url(self):
"""get_absolute_url must be implemented"""
url = r('subscriptions:detail', self.obj.pk)
self.assertEqual(url, self.obj.get_absolute_url())
def test_relation_with_other_model(self):
contact = Contact.objects.create(subscription=self.obj, value='email@example.com')
self.assertTrue(Contact.objects.exists())
def test_validation(self):
"""Contact kind should be limited to E or P"""
contact = Contact(kind='A', value='B')
self.assertRaises(ValidationError, contact.full_clean)
def test_description_can_be_blank(self):
field = Subscription._meta.get_field('description')
self.assertTrue(field.blank)
def test_description_can_be_null(self):
field = Subscription._meta.get_field('description')
self.assertTrue(field.null)
def test_has_contacts(self):
"""Subscription has contacts"""
self.obj.contacts.create(dict())
self.assertEqual(1, self.obj.contacts.count())
def test_ordering(self):
self.assertListEqual(['created'], Subscription._meta.ordering)
self.assertListEqual(['-created'], Subscription._meta.ordering)
class ContactManagerTest(TestCase):
def setUp(self):
s = Speaker.objects.create()
s.contact_set.create(kind=Contact.EMAIL, value='email@example.com')
s.contact_set.create(kind=Contact.PHONE, value='00-111112222')
def test_manager(self):
self.assertIsInstance(Contact.objects, ContactManager)
def test_emails(self):
qs = Contact.objects.emails()
expected = ['email@example.com']
self.assertQuerysetEqual(qs, expected, lambda o: o.value)
def test_phones(self):
qs = Contact.objects.phones()
expected = [00-111112222]
self.assertQuerysetEqual(qs, expected, lambda o: o.value)