Testando aplicações Django¶
O teste automatizado é uma ferramenta extremamente útil para eliminar bugs utilizada pelo desenvolvedor Web moderno. Você pode usar uma coleção de testes – uma test suite – para resolver, ou evitar, vários problemas:
- Quando você está escrevendo um código novo, pode usar os testes para verificar se seu código funciona como esperado.
- Quando está refatorando ou modificando um código antigo, você pode usar os testes para garantir que suas mudanças não afetaram inesperadamente o comportamento de sua aplicação.
Testar uma aplicação Web é uma tarefa complexa, porque uma aplicação Web é feita de várias camadas de lógica – da manipulação de uma requisição em nível HTTP, para a validação e processamento de formulário, para a renderização de template. Com o framework de teste-execução do Django e outros utilitários, você pode simular requisições, inserir dados de teste, inspecionar a saída de sua aplicação e freqüentemente verificar se seu código está fazendo o que deveria.
A melhor parte é que isso tudo é muito fácil.
Este documento é dividido em duas duas seções. Na primeira, explicamos como escrever testes com Django e, posteriormente, explicamos como rodá-los.
Escrevendo testes¶
Existem duas maneiras de se escrever testes com Django, correspondendo com os dois frameworks de teste que estão na biblioteca padrão do Python, que são:
Doctests – os testes estão embutidos nas docstrings (strings de documentação) de suas funções e são escritas de tal maneira a emular uma sessão do interpretador interativo do Python. Por exemplo:
def my_func(a_list, idx): """ >>> a = ['larry', 'curly', 'moe'] >>> my_func(a, 0) 'larry' >>> my_func(a, 1) 'curly' """ return a_list[idx]
Unit tests – (Testes unitários) são testes que são expressados como métodos em uma classe Python que é uma subclasse de
unittest.TestCase
. Por exemplo:import unittest class MyFuncTestCase(unittest.TestCase): def testBasic(self): a = ['larry', 'curly', 'moe'] self.assertEquals(my_func(a, 0), 'larry') self.assertEquals(my_func(a, 1), 'curly')
Você pode escolher o framework de teste que te agrade, dependendo da sintaxe que preferir, ou você pode misturar os dois, utilizando um framework para parte de seu código e outro framework para outra parte. Você também pode usar qualquer outro framework de testes Python, como explicaremos adiante.
Escrevendo doctests¶
Os doctests usam o módulo doctest padrão do Python, que procura em suas docstrings por instruções que se pareçam com uma sessão do interpretador interativo do Python. Uma explicação completa de como funciona o doctest está fora do escopo deste documento; leia a documentação oficial do Python para maiores detalhes.
O que é uma docstring?
Uma boa explicação de docstrings (e algumas diretrizes para utilizá-la de maneira completa) pode ser encontrada em PEP 257:
Uma docstring é uma literal de string que ocorre como a primeira instrução em um módulo, função, classe, ou definição de método. Esta docstring torna-se o atributo especial__doc__
do objeto em questão.
Por exemplo, esta função possui uma docstring que descreve o que ela faz:
def add_two(num):
"Retorna o resultado da adição de dois ao número informado."
return num + 2
Pelo motivo de que testes freqüentemente geram uma boa documentação, colocar testes diretamente nas suas docstrings é uma maneira eficiente de documentar e testar o seu código.
Para uma dada aplicação Django, o executor de testes procura por doctests em dois lugares:
- No arquivo
models.py
. Você pode definir doctests para o módulo todo e/ou um doctest para cada modelo. É uma pratica comum colocar doctests de nível de aplicação numa docstring do módulo e doctests de nível de modelo em docstrings de modelos. - Um arquivo chamado
tests.py
no diretório da aplicação – ou seja, o diretório que contémmodels.py
. Esse arquivo é umhook
para todos e quaisquer doctests que você queira escrever que não sejam necessariamente relacionados a modelos.
Aqui vai um exemplo de doctest de modelo:
# models.py
from django.db import models
class Animal(models.Model):
"""
An animal that knows how to make noise
# Create some animals
>>> lion = Animal.objects.create(name="lion", sound="roar")
>>> cat = Animal.objects.create(name="cat", sound="meow")
# Make 'em speak
>>> lion.speak()
'The lion says "roar"'
>>> cat.speak()
'The cat says "meow"'
"""
name = models.CharField(max_length=20)
sound = models.CharField(max_length=20)
def speak(self):
return 'The %s says "%s"' % (self.name, self.sound)
Quando você roda seus testes, o executor de testes irá encontrar essa docstring – note que pedaços dela parecem uma sessão interativa de Python – e executar suas linhas verificando se os resultados batem.
No caso de testes de modelo, note que o executor de testes cuida de criar seu
próprio banco de dados. Ou seja, qualquer teste que acesse banco de dados –
criando e gravando instâncias de modelos, por exemplo – não afetará seu banco
de dados em produção. Cada doctest inicia com um banco de dados novo contendo
uma tabela vazia para cada modelo. (Veja a seção de fixtures, abaixo, para mais
detalhes.) Note que para usar esse recurso, o usuário que o Django utiliza para
se conectar ao banco de dados deve ter direito de criar novos bancos CREATE
DATABASE
.
Para mais detalhes sobre como funciona o doctest, veja a documentação da biblioteca padrão para doctest
Escrevendo testes unitários¶
Como doctests, os testes unitários do Django usam um módulo da biblioteca padrão: unittest. Esse módulo usa uma maneira diferente de definir testes, utilizando um método baseado em classes.
Como nos doctests, para uma dada aplicação Django, o executor de testes procura por testes unitários em dois lugares:
- O arquivo
models.py
. O executor de testes procura por qualquer subclasse deunittest.TestCase
nesse módulo. - Um arquivo chamado
tests.py
no diretório da aplicação – o mesmo que contémmodels.py
. Novamente, o executor de testes procura por qualquer subclasse deunittest.TestCase
nesse módulo.
Este exemplo de subclasse de unittest.TestCase
é equivalente ao exemplo
dado na seção de doctest acima:
import unittest
from myapp.models import Animal
class AnimalTestCase(unittest.TestCase):
def setUp(self):
self.lion = Animal.objects.create(name="lion", sound="roar")
self.cat = Animal.objects.create(name="cat", sound="meow")
def testSpeaking(self):
self.assertEquals(self.lion.speak(), 'The lion says "roar"')
self.assertEquals(self.cat.speak(), 'The cat says "meow"')
Quando você roda seus testes, o comportamento padrão do
utilitário de teste é encontrar todos os test cases (ou seja, subclasses de
unittest.TestCase
) nos arquivos models.py
e tests.py
,
automaticamente montar uma test suite (conjunto de testes) destes test cases, e
rodá-la.
Há uma segunda maneira de se definir um test suite para um módulo: se você
define uma função chamada suite()
seja em models.py
ou tests.py
, o
executor de testes do Django usará essa função para construir a test suite para
o módulo. Isso segue a organização sugerida para testes unitários. Veja a
documentação do Python para mais detalhes de como construir uma test suite
complexa.
Para mais detalhes sobre unittest
, veja a documentação de unittest da
biblioteca padrão.
Qual devo usar?¶
Pelo motivo de o Django suportar ambos os frameworks de testes padrão do Python, cabe a você, de acordo com seu gosto, decidir qual utilizar. Você pode até mesmo decidir usar ambos.
Para desenvolvedores novatos em testes, essa escolha pode parecer confusa. Aqui estão algumas diferenças chave para ajudá-lo a decidir qual método é melhor:
Se você já utiliza Python por algum tempo, o
doctest
provavelmente parecerá mais “pythônico”. Ele foi projetado para tornar a escrita de testes o mais fácil possível, então não requer nenhum esforço extra de escrever classes ou métodos. Você simplesmente coloca seus testes em docstrings. Isso tem a vantagem adicional de servir como documentação (e documentação correta!).Se você está iniciando com testes, utilizar doctests farão com que você obtenha resultados mais rapidamente.
O framework
unittest
provavelmente será bem familiar para desenvolvedores vindos do Java. Ounittest
é inspirado pelo JUnit do Java, então você se sentirá em casa com esse método se você já usou o JUnit ou qualquer outro framework de testes inspirado no JUnit.Se você precisa escrever muitos testes que compartilham código parecido, então você vai gostar da organização baseada em classes e métodos do framework
unittest
. Ele facilita abstrair tarefas comuns em métodos comuns. O framework também suporta configuração explícita e/ou limpeza de rotinas, que te dão um alto nível de controle sobre o ambiente no qual seus testes são executados.
De novo, lembre-se de que você pode usar ambos os sistemas lado a lado (até mesmo em uma mesma aplicação). No final, a maioria dos projetos provavelmente acabará utilizando ambos. Cada um se destaca em diferentes circunstâncias.
Rodando os testes¶
Uma vez que você escreveu os testes, execute-os utilizando o utilitário do seu
projeto manage.py
:
$ ./manage.py test
Por padrão, isso executará cada teste em cada aplicação em
INSTALLED_APPS
. Se você só quer rodar os testes para uma aplicação em
particular, adicione o nome da aplicação à linha de comando. Por exemplo, se seu
INSTALLED_APPS
contém 'myproject.polls'
e
'myproject.animals'
, você pode rodar somente os testes unitários de
myproject.animals
com este comando:
# ./manage.py test animals
Note que utilizamos animals
, e não myproject.animals
.
Se você utiliza testes unitários, em vez de doctests, você pode ser ainda mais
específico na escolha de quais testes executar. Para rodar um único teste em uma
aplicação (por exemplo, o AnimalTestCase
descrito na seção “Escrevendo
testes unitários”), adicione o nome do test case à linha de comando:
$ ./manage.py test animals.AnimalTestCase
E pode ficar ainda mais granular que isso! Para rodar um único método de teste dentro de um test case, adicione o nome do método de teste:
$ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
O banco de dados de teste¶
Testes que necessitam de uma base de dados (nomeadamente, testes de modelo) não usarão seu banco de dados “real” (produção). Um banco de dados vazio é criado em separado para os testes.
Independentemente de os testes passarem ou falharem, o banco de dados de teste é destruído quando todos os testes forem executados.
Por padrão, o nome deste banco de dados de teste é o valor da configuração
DATABASE_NAME
adicionando o prefixo test_
. Quando um banco de
dados SQLite é utilizado, os testes usarão bancos de dados na memória (ou seja,
todo o banco será criado somente na memória, não utilizando nada do sistema de
arquivos). Se você quiser usar um nome diferente para o banco de dados de teste,
especifique o parâmetro de configuração TEST_DATABASE_NAME
.
Além de utilizar um banco de dados separado, o executor de testes usará os
mesmos parâmetros de banco de dados do seu arquivo de configuração:
DATABASE_ENGINE
, DATABASE_USER
, DATABASE_HOST
,
etc. O banco de dados de teste é criado pelo usuário especificado em
DATABASE_USER
, então é necessário garantir que esta conta de usuário
tem privilégios suficientes para criar um novo banco de dados no sistema.
Para um controle apurado sobre a codificação de caractere de seu banco de dados
de teste, use o parâmetro de configuração TEST_DATABASE_CHARSET
. Se
você está utilizando MySQL, você pode também usar o parâmetro
TEST_DATABASE_COLLATION
para controlar uma collation particular
utilizada pelo banco de dados de teste. Veja a
documentação de configurações para mais detalhes dessas
configurações avançadas.
Outras condições de testes¶
Independentemente do valor do DEBUG
do seu arquivo de configuração,
todos os testes do Django rodam com DEBUG=False
. Isto é para
assegurar que a saída observada de seu código seja igual ao da aplicação em
produção.
Entendendo a saída do teste¶
Quando roda seus testes, você visualiza um número de mensagens à medida que o
executor de testes se prepara. Você pode controlar o nível de detalhe dessas
mensagens com a opção de linha de comando verbosity
:
Creating test database...
Creating table myapp_animal
Creating table myapp_mineral
Loading 'initial_data' fixtures...
No fixtures found.
Isso informa que o executor de testes está criando um banco de teste, como descrito na seção anterior.
Uma vez que o banco de testes foi criado, o Django rodará os seus testes. Se tudo correr bem, você verá algo do tipo:
----------------------------------------------------------------------
Ran 22 tests in 0.221s
OK
Se existirem falhas, entretanto, você verá os detalhes completos sobre quais testes falharam:
======================================================================
FAIL: Doctest: ellington.core.throttle.models
----------------------------------------------------------------------
Traceback (most recent call last):
File "/dev/django/test/doctest.py", line 2153, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for myapp.models
File "/dev/myapp/models.py", line 0, in models
----------------------------------------------------------------------
File "/dev/myapp/models.py", line 14, in myapp.models
Failed example:
throttle.check("actor A", "action one", limit=2, hours=1)
Expected:
True
Got:
False
----------------------------------------------------------------------
Ran 2 tests in 0.048s
FAILED (failures=1)
Uma explicação completa sobre essa saída de erro está fora do escopo deste
documento, mas ela é bem intuitiva. Você pode consultar a documentação da
biblioteca unittest
do Python para maiores detalhes.
Note que o código retornado pelo script executor de testes é o número total de testes que falharam. Se todos os testes passarem, o código de retorno é 0. Este recurso é útil se você está usando script executor de testes em um script shell e precisa verificar o sucesso ou falha naquele nível.
Ferramentas de teste¶
O Django provê um pequeno conjunto de ferramentas que são uma verdadeira mão- na-roda na hora de escrever os testes.
O cliente de teste¶
O cliente de teste é uma classe Python que age como um navegador Web, permitindo a você testar suas views e interagir com suas aplicações feitas em Django programaticamente.
Algumas das coisas que você pode fazer com o cliente de teste são:
- Simular requisições GET e POST em uma URL e observar a resposta – tudo desde o nível baixo do HTTP (os cabeçalhos de resultado e códigos de status) até o conteúdo da página.
- Testar se a view correta é executada para uma dada URL.
- Testar que uma dada requisição é gerada por um determinado template, com um determinado contexto de template que contém certos valores.
Note que este cliente de teste não tem o propósito de ser um substituto para Twill, Selenium, ou outros frameworks “in-browser”. O cliente de teste do Django tem um foco diferente. Em resumo:
- Use o cliente de teste do Django para atestar que a view correta está sendo chamada e que a view está recebendo os dados de contexto corretos.
- Use frameworks in-browser como Twill e Selenium para testar HTML gerados e comportamento de páginas Web, ou seja, funcionalidade JavaScript.
Uma test suite completa deve usar uma combinação de ambos tipos de testes.
Visão geral e um exemplo rápido¶
Para usar o cliente de teste, instancie django.test.client.Client
e acesse
páginas Web:
>>> from django.test.client import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...'
Como esse exemplo sugere, você pode instanciar Client
de dentro de uma
sessão do interpretador interativo do Python.
Note algumas coisas importantes sobre como o cliente de teste funciona:
O cliente de teste não requer que o servidor Web esteja rodando. Aliás, ele rodará muito bem sem nenhum servidor Web! Isso se deve ao fato de que ele evita o esforço extra do HTTP e lida diretamente com o framework Django. Isso faz com que os testes unitários rodem bem mais rápido.
Ao acessar as páginas, lembre-se de especificar o caminho da URL, e não todo o domínio. Por exemplo, isto é correto:
>>> c.get('/login/')
E isto incorreto:
>>> c.get('http://www.example.com/login/')
O cliente de testes não é capaz de acessar páginas Web que não fazem parte do seu projeto Django. Se você precisa acessar outras páginas Web, utilize algum módulo da biblioteca padrão do Python como urllib ou urllib2.
Para resolver URLs, o cliente de testes usa o URLconf que está especificado no parâmetro de configuração
ROOT_URLCONF
.Apesar de o exemplo acima funcionar no interpretador interativo do Python, algumas funcionalidades do cliente de teste, notadamente as relacionadas a templates, somente estão disponíveis enquanto os testes estão sendo rodados.
O motivo disso é porque o executor de testes do Django faz um pouco de magia negra para determinar qual template foi carregado por uma determinada view. Essa magia negra (essencialmente um patch no sistema de templates na memória) acontece somente durante a execução dos testes.
Fazendo requisições¶
Use a classe django.test.client.Client
para fazer as requisições. Ela não
requer argumentos em sua contrução:
-
class
Client
¶ Uma vez que você tenha uma instância de
Client
, você pode chamar qualquer um dos seguintes métodos:-
get
(path, data={}, **extra)¶ Faz uma requisição GET no
path
informado e devolve um objetoResponse
, que está documentado abaixo.Os pares de chave-valor no dicionário
data
são usados para criar os dados que serão enviados via GET. Por exemplo:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
...resultará em uma requisição GET equivalente a:
/customers/details/?name=fred&age=7
The
extra
keyword arguments parameter can be used to specify headers to be sent in the request. For example:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, ... HTTP_X_REQUESTED_WITH='XMLHttpRequest')
...will send the HTTP header
HTTP_X_REQUESTED_WITH
to the details view, which is a good way to test code paths that use thedjango.http.HttpRequest.is_ajax()
method.
-
post
(path, data={}, content_type=MULTIPART_CONTENT, **extra)¶ Faz uma requisição POST no
path
informado e devolve um objetoResponse
, que está documentado abaixo.Os pares de chave-valor no dicionário
data
são usados para enviar os dados via POST. Por exemplo:>>> c = Client() >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
...resultará em uma requisição POST a esta URL:
/login/
...com estes dados de POST:
name=fred&passwd=secret
Se você informa o
content_type
(ex:text/xml
para um XML), o conteúdo dedata
será enviado como está na requisição POST, utilizandocontent_type
no cabeçalho HTTPContent-Type
.Se você não informa um valor para
content_type
, os valores emdata
serão transmitidos como um tipo de conteúdomultipart/form-data
. Nesse caso, os pares chave-valor emdata
serão codificados como uma mensagem multipart e usados para criar os dados de POST.Para enviar múltiplos valores para uma chave – por exemplo, para especificar as seleções para um
<select multiple>
– informe os valores como uma lista ou tupla para a chave. Por exemplo, este valor dedata
enviará três valores selecionados para o campo de nomechoices
:{'choices': ('a', 'b', 'd')}
Enviar arquivos é um caso especial. Para postar um arquivo, você só precisa informar o nome do campo como chave, e um manipulador de arquivo apontando para o arquivo que deseja postar como valor. Por exemplo:
>>> c = Client() >>> f = open('wishlist.doc') >>> c.post('/customers/wishes/', {'name': 'fred', 'attachment': f}) >>> f.close()
(O nome
attachment
aqui não é relevante; use qualquer nome que o seu código de processamento de arquivo espera.)Perceba que você deve fechar o arquivo manualmente depois que ele foi informado para o
post()
.The
extra
argument acts the same as forClient.get()
.
-
login
(**credentials)¶ -
Se seu site Django usa o sistema de autenticação e você precisa logar usuários, você pode usar o método
login()
do cliente de testes para simular o efeito de um usuário logando no site.Depois de chamar este método, o cliente de testes terá todos os cookies e dados de sessão necessários para passar a qualquer teste baseado em login que faça parte de uma view.
O formato do argumento
credentials
depende de qual backend de autenticação você está usando (que é configurado pelo parâmetro de configuraçãoAUTHENTICATION_BACKENDS
). Se você está usando o backend padrão de autenticação do Django (ModelBackend
),credentials
deve ser o nome do usuário e a senha, informados como argumentos nomeados:>>> c = Client() >>> c.login(username='fred', password='secret') >>> # Agora você pode acessar a view que somente está disponível >>> # para usuários logados.
Se você está usando um backend de autenticação diferente, este método requer credenciais diferentes. Ele requer as mesmas credenciais que o método
authenticate()
do backend.login()
devolveTrue
se as credenciais foram aceitas e o login ocorreu com sucesso.Finalmente, você precisa lembrar-se de criar contas de usuários antes de utilizar este método. Como explicado acima, o executor de testes utiliza uma base de dados de teste, que não contém usuários criados. Como resultado, contas que são válidas no seu site em produção não funcionarão nas condições de teste. Você precisa criar os usuários como parte de sua test suite – tanto manualmente (utilizando a API de modelos do Django) ou com uma test fixture.
Lembre-se que se você quiser que seu usuários de teste tenha uma senha, você não pode setar a senha do usuário usando o atributo password diretamente – você deve usar a função
set_password()
para armazenar corretamente uma senha criptografada. Alternativamente, você pode usar o método helpercreate_user()
para criar um novo usuário com a senha criptografada corretamente.
-
logout
()¶ -
Se o seu site usa o sistema de autenticação do Django, o método
logout()
pode ser utilizado para simular o efeito de um usuário se deslogar do seu site.Depois de chamar este método, o cliente de teste terá todos os cookies e dados de sessão retornados para os valores padrões. Requisições subseqüentes parecerão vir de um usuário anônimo AnonymousUser.
-
Testando as respostas¶
Ambos os métodos get()
e post()
devolvem um objeto Response
. Este
objeto Response
não é o mesmo que o objeto HttpResponse
retornado
pelas views do Django; o objeto de resposta do teste tem dados adicionais
úteis para serem verificados pelo código de teste.
Especificamente, o objeto Response
tem os seguintes atributos:
-
class
Response
¶ -
client
¶ O cliente de teste que foi usado para fazer a requisição que resultou na resposta.
-
content
¶ O corpo da resposta, como uma string. Este é o conteúdo final da página que foi gerada pela view, ou alguma mensagem de erro.
-
context
¶ A instância
Context
que foi utilizada pelo template para produzir o conteúdo da resposta.Se a página gerada utilizou múltiplos templates, então o
context
será uma lista de objetosContext
, na ordem em que foram utilizados.
-
request
¶ Os dados da requisição que provocaram a resposta.
-
status_code
¶ O status da resposta HTTP, como um inteiro. Veja RFC2616 para uma lista completa de códigos de status HTTP.
-
template
¶ A instância de
Template
que foi utilizada para gerar o conteúdo final. Usetemplate.name
para obter o nome do arquivo do template, se o template foi carregado de um arquivo. (O nome é uma string como'admin/index.html'
.)Se a página gerada utilizou vários templates – ex: utilizando herança de templates – então
template
será uma lista de instâncias deTemplate
, na ordem em que eles foram utilizados.
-
Você pode também usar uma sintaxe de dicionário no objeto de resposta para
obter o valor de qualquer configuração nos cabeçalhos HTTP. Por exemplo, você
pode determinar o conteúdo de uma resposta usando response['Content-Type']
.
Exceções¶
Se você aponta o cliente de testes para uma view que lança uma exceção, esta
exceção estará visível no test case. Você pode então usar blocos try...catch
ou unittest.TestCase.assertRaises()
para testar por exceções.
As únicas exceções que não estão visíveis ao cliente de teste são Http404
,
PermissionDenied
e SystemExit
. O Django captura estas exceções
internamente e converte-as em códigos de resposta HTTP adequados. Nesses casos,
você pode verificar response.status_code
no seu código.
Estado persistente (persistent state)¶
O cliente de teste é “stateful”, ou seja, mantém o estado. Se uma resposta
retorna um cookie, então esse cookie será armazenado no cliente de teste e
enviado com todas as requisições subseqüentes de get()
e post()
.
Políticas de expiração desses cookies não são seguidas. Se você quiser que um
cookie expire, delete-o manualmente ou crie uma nova instância de Client
(o que irá deletar todos os cookies).
Um cliente de teste possui dois atributos que armazenam informações de estado persistente. Você pode acessar essas propriedades como parte de uma condição de teste.
Um objeto
SimpleCookie
Python, contendo os valores atuais de todos os cookies do cliente. Veja a Documentação do módulo cookie para saber mais.
-
Client.
session
¶ A dictionary-like object containing session information. See the session documentation for full details. Um objeto que se comporta como dicionário contendo informações da sessão. Veja a documentação da sessão para maiores detalhes.
Exemplo¶
Segue um teste unitário simples usando o cliente de teste:
import unittest
from django.test.client import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Cada teste precisa de um cliente.
self.client = Client()
def test_details(self):
# Faz uma requisição GET.
response = self.client.get('/customer/details/')
# Verifica se a resposta foi 200 OK.
self.failUnlessEqual(response.status_code, 200)
# Verifica se o contexto utilizado contém 5 customers.
self.failUnlessEqual(len(response.context['customers']), 5)
TestCase¶
Uma classe de teste unitário normal do Python extende uma classe base
unittest.TestCase
. O Djagno provê uma extensão desta classe:
-
class
TestCase
¶
Esta classe provê algumas capacidades adicionais qeu podem ser úteis para testar site Web.
Converter um unittest.TestCase
normal para um Django TestCase
é fácil:
basta mudar a classe base de seu teste de unittest.TestCase
para
django.test.TestCase
. Todas as funcionalidades padrões de teste unitário do
Python continuarão disponíveis, mas serão aumentadas com algumas adições úteis.
O cliente de teste default¶
-
TestCase.
client
¶
Cada teste em uma instância de django.test.TestCase
tem acesso a uma
instância do cliente de testes do Django. Esse cliente pode ser acessado como
self.client
. O cliente é recriado para cada teste, então você não tem de
se preocupar sobre o estado (como os cookies) ser levado de um teste a outro.
Isso significa que, em vez de instanciar um Client
em cada teste:
import unittest
from django.test.client import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get('/customer/details/')
self.failUnlessEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.failUnlessEqual(response.status_code, 200)
...você só precisa se referir a self.client
, como:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get('/customer/details/')
self.failUnlessEqual(response.status_code, 200)
def test_index(self):
response = self.client.get('/customer/index/')
self.failUnlessEqual(response.status_code, 200)
Carga de fixture¶
-
TestCase.
fixtures
¶
Um teste para um site Web com banco de dados não tem muita utilidade se não
existir dados no banco. Para facilitar a carga destes dados no banco, a classe
TestCase
do Django proporciona uma maneira de carregar fixtures.
Uma fixture é uma coleção de dados que o Django sabe como importar para um banco de dados. Por exemplo, se o seu site tem contas de usuários, você pode configurar uma fixture de usuários no intuito de popular seu banco durante os testes.
O método mais simples e direto de criar uma fixture é usar o comando
manage.py dumpdata
. Isso assume que você já tem algum dado em seu banco.
Veja a documentação do dumpdata
para mais detalhes.
Note
Se você já rodou alguma vez o manage.py syncdb
, você já usou fixture
sem nem mesmo saber! Quando você dá um syncdb
no banco de dados pela
primeira vez, o Django instala uma fixture chamada initial_data
.
Isso dá a possibilidade de popular um banco de dados novo com qualquer dado
relacional, como um conjunto padrão de categorias, por exemplo.
Fixtures com outros nomes podem sempre ser instaladas manualmente usando
o comando manage.py loaddata
.
Uma vez que você criou uma fixture e colocou em algum lugar em seu projeto
Django, você pode utilizá-la nos seus testes unitários especificando um
atributo de classe fixtures
na sua subclasse de django.test.TestCase
:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Definições de testes como anteriormente.
def testFluffyAnimals(self):
# Um teste que usa fixtures.
Eis o que acontecerá nesse caso:
- No início de cada test case, antes de
setUp()
ser rodado, o Django limpará o banco de dados, retornando um banco de dados no estado que estava diretamente após a chamada desyncdb
. - Em seguida, todas as fixtures nomeadas são instaladas. Nesse exemplo, o
Django instalará qualquer fixture JSON chamada
mammals
, seguida por qualquer fixture chamadabirds
. Veja adocumentação do loaddata
para mais detalhes sobre a definição e a instalação de fixtures.
Esse procedimento de limpeza/carga é repetido para cada teste no test case, então você pode ter certeza de que o resultado de um teste não será afetado por outro teste, ou pela ordem de execução dos testes.
Configuração do URLconf¶
-
TestCase.
urls
¶
Se a sua aplicação possui views, você pode querer incluir testes que utilizam o cliente de testes para exercitá-las. Entretanto, um usuário final é livre para configurar as views em sua aplicação em qualquer URL de sua preferência. Isso significa que seus testes não podem contar com o fato de que suas views estarão disponíveis em uma URL em particular.
A fim de proporcionar URLs confiáveis para seu teste, django.test.TestCase
tem a capacidade de customizar a configuração do URLconf pelo período de
execução dos testes. Se sua instância de TestCase
define o atributo
urls
, o TestCase
usará os valores deste atributo como o
ROOT_URLCONF
pelo período de execução do teste.
Por exemplo:
from django.test import TestCase
class TestMyViews(TestCase):
urls = 'myapp.test_urls'
def testIndexPageView(self):
# Aqui você testará sua view usando ``Client``.
Esse test case utilizará o conteúdo de myapp.test_urls
como o URLconf
durante a execução do teste.
Esvaziando a caixa de saída de teste¶
Se você usa a classe Django TestCase
, o executor de testes limpará o
conteúdo da caixa de saída de e-mail no início de cada teste.
Para mais detalhes sobre os serviços de e-mail durante os teste, veja Serviços de e-mail.
Assertions¶
Como numa classe Python unittest.TestCase
normal que implementa métodos de
asserção como assertTrue
e assertEquals
, a classe customizada
TestCase
do Django disponibiliza alguns métodos de asserção customizados
que são úteis para testar aplicações Web:
-
TestCase.
assertContains
(response, text, count=None, status_code=200)¶ Testa se uma instância de
Response
produziu ostatus_code
informado e que otext
aparece no conteúdo da resposta. Secount
é fornecido,text
deve ocorrer exatamentecount
vezes na resposta.
-
TestCase.
assertNotContains
(response, text, status_code=200)¶ Testa se uma instância de
Response
produziu ostatus_code
informado e que otext
não aparece no conteúdo da resposta.
-
TestCase.
assertFormError
(response, form, field, errors)¶ Testa se um campo no formulário lança a lista de erros fornecida quando gerado no formulário.
form
é o nome da instância deForm
informada ao contexto do template.field
é o nome do campo no formulário para verificar. Sefield
tem um valor deNone
, erros não relacionados a campos (erros que você pode acessar viaform.non_field_errors()
) serão verificados.errors
é uma string de erro, ou uma lista de strings de erro, que são esperados como resultado da validação do formulário.
-
TestCase.
assertTemplateUsed
(response, template_name)¶ -
Testa se o template com o nome informado foi usado na geração da resposta.
O nome é uma string como
'admin/index.html'
.
-
TestCase.
assertTemplateNotUsed
(response, template_name)¶ Testa se o template com o nome informado não foi usado na geração da resposta.
-
TestCase.
assertRedirects
(response, expected_url, status_code=302, target_status_code=200)¶ Teste se a resposta devolve um status de redirecionamento
status_code
, se redirecionou para a URLexpected_url
(incluindo quaisquer dados GET), e a página subseqüente foi recebida com otarget_status_code
.
Serviços de e-mail¶
Se alguma de suas views manda e-mail usando a Funcionalidade de e-mail do Django, você provavelmente não quer mandar e-mail cada vez que você roda um teste utilizando a view. Por esse motivo, o executor de testes do Django automaticamente redireciona todos e-mails enviados por meio do Django para uma caixa de saída fictícia. Isso deixa você testar cada aspecto do envio de e-mail – do númeto de mensagens enviadas ao conteúdo de cada mensagem – sem ter de enviar as mensagens de verdade.
O executor de testes consegue fazer isso de forma transparente trocando a
classe <~django.core.mail.SMTPConnection>
normal por uma versão
diferente. (Não se preocupe – isso não tem efeito em quaisquer outros meios de
envio de e-mail fora do Django, como o servidor de e-mail de sua máquina, se
estiver rodando um).
-
django.core.mail.
outbox
¶
Durante a execução dos testes, cada e-mail de saída é gravado em
django.core.mail.outbox
. Esta é uma lista simples de todas as instâncias
de <~django.core.mail.EmailMessage>
que foram enviadas. Ela não existem
nas condições normais de execução, ou seja, quando você não está rodando testes
unitários. A caixa de saída é criada durante a configuração do teste, junto com
a <~django.core.mail.SMTPConnection>
fictícia. Quando o framework de
teste é encerrado, a classe <~django.core.mail.SMTPConnection>
padrão é
restaurada, e a caixa de saída de teste é destruída.
The outbox
attribute is a special attribute that is created only when
the tests are run. It doesn’t normally exist as part of the
django.core.mail
module and you can’t import it directly. The code
below shows how to access this attribute correctly.
Aqui vai um exemplo de teste que verifica o tamanho e conteúdo de
django.core.mail.outbox
:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Envia mensagem.
mail.send_mail('Assunto aqui', 'Aqui vai a mensagem.',
'from@example.com', ['to@example.com'],
fail_silently=False)
# Verifica se uma mensagem foi enviada.
self.assertEqual(len(mail.outbox), 1)
# Verifica se o assunto da mensagem é igual
self.assertEqual(mail.outbox[0].subject, 'Assunto aqui')
Como dito anteriormente, a caixa de saída de teste
é esvaziada no início de cada teste em um TestCase
Django. Para esvaziar a
caixa de saída manualmente, atribua uma lista vazia para mail.outbox
:
from django.core import mail
# Esvazia a caixa de saída
mail.outbox = []
Utilizando frameworks de testes diferentes¶
Obviamente, doctest
e unittest
não são os únicos frameworks de testes
Python. Apesar de o Django não suportar explicitamente frameworks alternativos,
ele provê uma maneira de invocar testes construídos para um framework
alternativo como se fossem testes Django normais.
Quando você roda ./manage.py test
, o Django procura a configuração
TEST_RUNNER
para determinar o que fazer. Por padrão, o
TEST_RUNNER
aponta para 'django.test.simple.run_tests'
. Este
método define o comportamento padrão dos testes no Django. Esse comportamento
envolve:
- Fazer uma configuração global antes do teste.
- Criar o banco de dados de teste.
- Executar
syncdb
para instalar os modelos e dados iniciais no banco de dados de teste. - Procurar testes unitários e doctests nos arquivos
models.py
etests.py
em cada aplicação instalada. - Rodar os testes unitários e doctests que forem encontrados.
- Destruir o banco de dados de teste.
- Executar um encerramento global após o término dos testes.
Se você define seu próprio método executor de testes e aponta o
TEST_RUNNER
para este método, o Django rodará o seu executor de
testes toda vez que você rodar ./manage.py test
. Desta maneira, é possível
usar qualquer framework de testes que possa ser executado a partir de um código
Python.
Definindo o executor de testes¶
Por convenção, um executor de testes deve ser chamado run_tests
. O único
requisito é que ele tenha os mesmos argumentos que o executor de testes do
Django:
-
run_tests
(test_labels, verbosity=1, interactive=True, extra_tests=[])¶ test_labels
é uma lista de strings descrevendo os testes a serem rodados. Cada string pode ter uma das três formas a seguir:app.TestCase.test_method
– Executa um único método de teste em um test case.app.TestCase
– Executa todos os métodos de teste em um test case.app
– Procura e roda todos os testes na aplicação nomeada.
Se
test_labels
tem o valorNone
, o executor de testes deve procurar e rodar os testes para todas as aplicações emINSTALLED_APPS
.verbosity
determina a quantidade de notificações e informações de debug que serão apresentadas no console;0
significa sem saída,1
é saída normal, e2
é saída detalhada.Se
interactive
éTrue
, a test suite tem permissão para pedir ao usuário por instruções quando é executada. Um exemplo desse comportamento seria pedir permissão para excluir um teste existente do banco de dados. Seinteractive
éFalse
, a test suite deve ser capaz de rodar sozinha, sem intervenção manual.extra_tests
é uma lista de instâncias extra deTestCase
para adicionar à suite, que é rodada pelo executor de testes. Esses testes extras são executados além dos descobertos nos módulos listados emmodule_list
.Este método deve retornar o número de testes que falharam.
Utilitários de testes¶
Para ajudar na criação de seu próprio executor de testes, o Django possui
alguns métodos utilitários no módulo django.test.utils
.
-
setup_test_environment
()¶ Executa quaisquer configurações prévias aos testes, como a instalação de instrumentação para o sistema de templates e a configuração do
SMTPConnection
fictício.
-
teardown_test_environment
()¶ Executa quaisquer tarefas globais de encerramento após o término dos testes, como remover a magia negra do sistema de templates e restaurar os serviços normais de e-mail.
The creation module of the database backend (
connection.creation
) also provides some utilities that can be useful during testing.
-
create_test_db
(verbosity=1, autoclobber=False)¶ Cria um novo banco de dados e roda o
syncdb
nele.verbosity
tem o mesmo comportamento que emrun_tests()
.autoclobber
descreve o comportamento que acontecerá se um banco de dados com o mesmo nome que o banco de teste é encontrado:- Se
autoclobber
éFalse
, será pedido ao usuário para aprovar a destruição do banco de dados existente.sys.exit
é chamado se o usuário não aprovar. - Se
autoclobber
éTrue
, o banco de dados será destruído sem consultar o usuário.
Returns the name of the test database that it created.
create_test_db()
tem o efeito colateral de modificarsettings.DATABASE_NAME
para bater com o nome do banco de dados de teste.create_test_db()
agora retorna o nome do banco de dados de teste.- Se
-
destroy_test_db
(old_database_name, verbosity=1)¶ Destrói o banco de dados cujo nome está no parâmetro de configuração
DATABASE_NAME
e restaura o valor deDATABASE_NAME
para o nome fornecido.verbosity
tem o mesmo comportamento que emrun_tests()
.