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:
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.
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:
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')
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]
We'll discuss choosing the appropriate test framework later, however, most experienced developers prefer unit tests. Você também pode usar qualquer outro framework de testes Python, como explicaremos adiante.
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.
unittest2
Python 2.7 introduced some major changes to the unittest library, adding some extremely useful features. To ensure that every Django project can benefit from these new features, Django ships with a copy of unittest2, a copy of the Python 2.7 unittest library, backported for Python 2.4 compatibility.
To access this library, Django provides the django.utils.unittest module alias. If you are using Python 2.7, or you have installed unittest2 locally, Django will map the alias to the installed version of the unittest library. Otherwise, Django will use it's own bundled version of unittest2.
To use this alias, simply use:
from django.utils import unittest
wherever you would have historically used:
import unittest
If you want to continue to use the base unittest library, you can -- you just won't get any of the nice new unittest2 features.
Como nos doctests, para uma dada aplicação Django, o executor de testes procura por testes unitários em dois lugares:
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.
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:
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
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:
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
You can use the same rules if you're using doctests. Django will use the test label as a path to the test method or class that you want to run. If your models.py or tests.py has a function with a doctest, or class with a class-level doctest, you can invoke that test by appending the name of the test method or class to the label:
$ ./manage.py test animals.classify
If you want to run the doctest for a specific method in a class, add the name of the method to the label:
$ ./manage.py test animals.Classifier.run
If you're using a __test__ dictionary to specify doctests for a module, Django will use the label as a key in the __test__ dictionary for defined in models.py and tests.py.
If you press Ctrl-C while the tests are running, the test runner will wait for the currently running test to complete and then exit gracefully. During a graceful exit the test runner will output details of any test failures, report on how many tests were run and how many errors and failures were encountered, and destroy any test databases as usual. Thus pressing Ctrl-C can be very useful if you forget to pass the --failfast option, notice that some tests are unexpectedly failing, and want to get details on the failures without waiting for the full test run to complete.
If you do not want to wait for the currently running test to finish, you can press Ctrl-C a second time and the test run will halt immediately, but not gracefully. No details of the tests run before the interruption will be reported, and any test databases created by the run will not be destroyed.
Test with warnings enabled
It's a good idea to run your tests with Python warnings enabled: python -Wall manage.py test. The -Wall flag tells Python to display deprecation warnings. Django, like many other Python libraries, uses these warnings to flag when features are going away. It also might flag areas in your code that aren't strictly wrong but could benefit from a better implementation.
If you want to run tests outside of ./manage.py test -- for example, from a shell prompt -- you will need to set up the test environment first. Django provides a convenience method to do this:
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
This convenience method sets up the test database, and puts other Django features into modes that allow for repeatable testing.
The call to setup_test_environment() is made automatically as part of the setup of ./manage.py test. You only need to manually invoke this method if you're not using running your tests via Django's test runner.
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.
If you're testing a multiple database configuration with master/slave replication, this strategy of creating test databases poses a problem. When the test databases are created, there won't be any replication, and as a result, data created on the master won't be seen on the slave.
To compensate for this, Django allows you to define that a database is a test mirror. Consider the following (simplified) example database configuration:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myproject',
'HOST': 'dbmaster',
# ... plus some other settings
},
'slave': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myproject',
'HOST': 'dbslave',
'TEST_MIRROR': 'default'
# ... plus some other settings
}
}
In this setup, we have two database servers: dbmaster, described by the database alias default, and dbslave described by the alias slave. As you might expect, dbslave has been configured by the database administrator as a read slave of dbmaster, so in normal activity, any write to default will appear on slave.
If Django created two independent test databases, this would break any tests that expected replication to occur. However, the slave database has been configured as a test mirror (using the TEST_MIRROR setting), indicating that under testing, slave should be treated as a mirror of default.
When the test environment is configured, a test version of slave will not be created. Instead the connection to slave will be redirected to point at default. As a result, writes to default will appear on slave -- but because they are actually the same database, not because there is data replication between the two databases.
By default, Django will always create the default database first. However, no guarantees are made on the creation order of any other databases in your test setup.
If your database configuration requires a specific creation order, you can specify the dependencies that exist using the TEST_DEPENDENCIES setting. Consider the following (simplified) example database configuration:
DATABASES = {
'default': {
# ... db settings
'TEST_DEPENDENCIES': ['diamonds']
},
'diamonds': {
# ... db settings
},
'clubs': {
# ... db settings
'TEST_DEPENDENCIES': ['diamonds']
},
'spades': {
# ... db settings
'TEST_DEPENDENCIES': ['diamonds','hearts']
},
'hearts': {
# ... db settings
'TEST_DEPENDENCIES': ['diamonds','clubs']
}
}
Under this configuration, the diamonds database will be created first, as it is the only database alias without dependencies. The default and clubs alias will be created next (although the order of creation of this pair is not guaranteed); then hearts; and finally spades.
If there are any circular dependencies in the TEST_DEPENDENCIES definition, an ImproperlyConfigured exception will be raised.
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.
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.
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 é 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:
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:
Uma test suite completa deve usar uma combinação de ambos tipos de testes.
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.
By default, the test client will disable any CSRF checks performed by your site.
If, for some reason, you want the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks. To do this, pass in the enforce_csrf_checks argument when you construct your client:
>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)
Use a classe django.test.client.Client para fazer as requisições. Ela não requer argumentos em sua contrução:
Uma vez que você tenha uma instância de Client, você pode chamar qualquer um dos seguintes métodos:
Faz uma requisição GET no path informado e devolve um objeto Response, 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 the django.http.HttpRequest.is_ajax() method.
CGI specification
The headers sent via **extra should follow CGI specification. For example, emulating a different "Host" header as sent in the HTTP request from the browser to the server should be passed as HTTP_HOST.
If you already have the GET arguments in URL-encoded form, you can use that encoding instead of using the data argument. For example, the previous GET request could also be posed as:
>>> c = Client()
>>> c.get('/customers/details/?name=fred&age=7')
If you provide a URL with both an encoded GET data and a data argument, the data argument will take precedence.
If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.
If you had an url /redirect_me/ that redirected to /next/, that redirected to /final/, this is what you'd see:
>>> response = c.get('/redirect_me/', follow=True)
>>> response.redirect_chain
[(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)]
Faz uma requisição POST no path informado e devolve um objeto Response, 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 de data será enviado como está na requisição POST, utilizando content_type no cabeçalho HTTP Content-Type.
Se você não informa um valor para content_type, os valores em data serão transmitidos como um tipo de conteúdo multipart/form-data. Nesse caso, os pares chave-valor em data 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 de data enviará três valores selecionados para o campo de nome choices:
{'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 for Client.get().
If the URL you request with a POST contains encoded parameters, these parameters will be made available in the request.GET data. For example, if you were to make the request:
>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})
... the view handling this request could interrogate request.POST to retrieve the username and password, and could interrogate request.GET to determine if the user was a visitor.
If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.
Makes a HEAD request on the provided path and returns a Response object. Useful for testing RESTful interfaces. Acts just like Client.get() except it does not return a message body.
If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.
Makes an OPTIONS request on the provided path and returns a Response object. Useful for testing RESTful interfaces.
If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.
The extra argument acts the same as for Client.get().
Makes a PUT request on the provided path and returns a Response object. Useful for testing RESTful interfaces. Acts just like Client.post() except with the PUT request method.
If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.
Makes an DELETE request on the provided path and returns a Response object. Useful for testing RESTful interfaces.
If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.
The extra argument acts the same as for Client.get().
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ção AUTHENTICATION_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() devolve True 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 helper create_user() para criar um novo usuário com a senha criptografada corretamente.
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.
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:
O cliente de teste que foi usado para fazer a requisição que resultou na resposta.
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.
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 objetos Context, na ordem em que foram utilizados.
Os dados da requisição que provocaram a resposta.
O status da resposta HTTP, como um inteiro. Veja RFC2616 para uma lista completa de códigos de status HTTP.
A instância de Template que foi utilizada para gerar o conteúdo final. Use template.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 de Template, 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'].
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.
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.
A dictionary-like object containing session information. See the session documentation for full details.
To modify the session and then save it, it must be stored in a variable first (because a new SessionStore is created every time this property is accessed):
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
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)
The RequestFactory shares the same API as the test client. However, instead of behaving like a browser, the RequestFactory provides a way to generate a request instance that can be used as the first argument to any view. This means you can test a view function the same way as you would test any other function -- as a black box, with exactly known inputs, testing for specific outputs.
The API for the RequestFactory is a slightly restricted subset of the test client API:
The following is a simple unit test using the request factory:
from django.utils import unittest
from django.test.client import RequestFactory
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
self.assertEqual(response.status_code, 200)
Uma classe de teste unitário normal do Python extende uma classe base unittest.TestCase. O Djagno provê uma extensão desta classe:
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, incluindo:
Django TestCase classes make use of database transaction facilities, if available, to speed up the process of resetting the database to a known state at the beginning of each test. A consequence of this, however, is that the effects of transaction commit and rollback cannot be tested by a Django TestCase class. If your test requires testing of such transactional behavior, you should use a Django TransactionTestCase.
TransactionTestCase and TestCase are identical except for the manner in which the database is reset to a known state and the ability for test code to test the effects of commit and rollback. A TransactionTestCase resets the database before the test runs by truncating all tables and reloading initial data. A TransactionTestCase may call commit and rollback and observe the effects of these calls on the database.
A TestCase, on the other hand, does not truncate tables and reload initial data at the beginning of a test. Instead, it encloses the test code in a database transaction that is rolled back at the end of the test. It also prevents the code under test from issuing any commit or rollback operations on the database, to ensure that the rollback at the end of the test restores the database to its initial state. In order to guarantee that all TestCase code starts with a clean database, the Django test runner runs all TestCase tests first, before any other tests (e.g. doctests) that may alter the database without restoring it to its original state.
When running on a database that does not support rollback (e.g. MySQL with the MyISAM storage engine), TestCase falls back to initializing the database by truncating tables and reloading initial data.
Note
The TestCase use of rollback to un-do the effects of the test code may reveal previously-undetected errors in test code. For example, test code that assumes primary keys values will be assigned starting at one may find that assumption no longer holds true when rollbacks instead of table truncation are being used to reset the database. Similarly, the reordering of tests so that all TestCase classes run first may reveal unexpected dependencies on test case ordering. In such cases a quick fix is to switch the TestCase to a TransactionTestCase. A better long-term fix, that allows the test to take advantage of the speed benefit of TestCase, is to fix the underlying test problem.
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)
If you want to use a different Client class (for example, a subclass with customized behavior), use the client_class class attribute:
from django.test import TestCase
from django.test.client import Client
class MyTestClient(Client):
# Specialized methods for your environment...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
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:
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.
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.
Django sets up a test database corresponding to every database that is defined in the DATABASES definition in your settings file. However, a big part of the time taken to run a Django TestCase is consumed by the call to flush that ensures that you have a clean database at the start of each test run. If you have multiple databases, multiple flushes are required (one for each database), which can be a time consuming activity -- especially if your tests don't need to test multi-database activity.
As an optimization, Django only flushes the default database at the start of each test run. If your setup contains multiple databases, and you have a test that requires every database to be clean, you can use the multi_db attribute on the test suite to request a full flush.
For example:
class TestMyViews(TestCase):
multi_db = True
def testIndexPageView(self):
call_some_test_code()
This test case will flush all the test databases before running testIndexPageView.
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.
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:
The failure messages given by the assertion methods can be customized with the msg_prefix argument. This string will be prefixed to any failure message generated by the assertion. This allows you to provide additional details that may help you to identify the location and cause of an failure in your test suite.
Testa se uma instância de Response produziu o status_code informado e que o text aparece no conteúdo da resposta. Se count é fornecido, text deve ocorrer exatamente count vezes na resposta.
Testa se uma instância de Response produziu o status_code informado e que o text não aparece no conteúdo da resposta.
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 de Form informada ao contexto do template.
field é o nome do campo no formulário para verificar. Se field tem um valor de None, erros não relacionados a campos (erros que você pode acessar via form.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.
O nome é uma string como 'admin/index.html'.
Testa se o template com o nome informado não foi usado na geração da resposta.
Teste se a resposta devolve um status de redirecionamento status_code, se redirecionou para a URL expected_url (incluindo quaisquer dados GET), e a página subseqüente foi recebida com o target_status_code.
If your request used the follow argument, the expected_url and target_status_code will be the url and status code for the final point of the redirect chain.
Asserts that a queryset qs returns a particular list of values values.
The comparison of the contents of qs and values is performed using the function transform; by default, this means that the repr() of each value is compared. Any other callable can be used if repr() doesn't provide a unique or helpful comparison.
The comparison is also ordering dependent. If qs doesn't provide an implicit ordering, you will need to apply a order_by() clause to your queryset to ensure that the test will pass reliably.
Asserts that when func is called with *args and **kwargs that num database queries are executed.
If a "using" key is present in kwargs it is used as the database alias for which to check the number of queries. If you wish to call a function with a using parameter you can do it by wrapping the call with a lambda to add an extra parameter:
self.assertNumQueries(7, lambda: my_function(using=7))
If you're using Python 2.5 or greater you can also use this as a context manager:
# This is necessary in Python 2.5 to enable the with statement, in 2.6
# and up it is no longer necessary.
from __future__ import with_statement
with self.assertNumQueries(2):
Person.objects.create(name="Aaron")
Person.objects.create(name="Daniel")
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).
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 = []
Skip the decorated test if the named database feature is not supported.
For example, the following test will not be executed if the database supports transactions (e.g., it would run under PostgreSQL, but not under MySQL with MyISAM tables):
class MyTests(TestCase):
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
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:
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.
A test runner is a class defining a run_tests() method. Django ships with a DjangoTestSuiteRunner class that defines the default Django testing behavior. This class defines the run_tests() entry point, plus a selection of other methods that are used to by run_tests() to set up, execute and tear down the test suite.
verbosity determines the amount of notification and debug information that will be printed to the console; 0 is no output, 1 is normal output, and 2 is verbose output.
If interactive is True, the test suite has permission to ask the user for instructions when the test suite is executed. An example of this behavior would be asking for permission to delete an existing test database. If interactive is False, the test suite must be able to run without any manual intervention.
If failfast is True, the test suite will stop running after the first test failure is detected.
Django will, from time to time, extend the capabilities of the test runner by adding new arguments. The **kwargs declaration allows for this expansion. If you subclass DjangoTestSuiteRunner or write your own test runner, ensure accept and handle the **kwargs parameter.
Run the test suite.
test_labels is a list of strings describing the tests to be run. A test label can take one of three forms:
If test_labels has a value of None, the test runner should run search for tests in all the applications in INSTALLED_APPS.
extra_tests is a list of extra TestCase instances to add to the suite that is executed by the test runner. These extra tests are run in addition to those discovered in the modules listed in test_labels.
This method should return the number of tests that failed.
Sets up the test environment ready for testing.
Constructs a test suite that matches the test labels provided.
test_labels is a list of strings describing the tests to be run. A test label can take one of three forms:
If test_labels has a value of None, the test runner should run search for tests in all the applications in INSTALLED_APPS.
extra_tests is a list of extra TestCase instances to add to the suite that is executed by the test runner. These extra tests are run in addition to those discovered in the modules listed in test_labels.
Returns a TestSuite instance ready to be run.
Creates the test databases.
Returns a data structure that provides enough detail to undo the changes that have been made. This data will be provided to the teardown_databases() function at the conclusion of testing.
Runs the test suite.
Returns the result produced by the running the test suite.
Destroys the test databases, restoring pre-test conditions.
old_config is a data structure defining the changes in the database configuration that need to be reversed. It is the return value of the setup_databases() method.
Restores the pre-test environment.
Computes and returns a return code based on a test suite, and the result from that test suite.
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.
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.
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.
Cria um novo banco de dados e roda o syncdb nele.
verbosity tem o mesmo comportamento que em run_tests().
autoclobber descreve o comportamento que acontecerá se um banco de dados com o mesmo nome que o banco de teste é encontrado:
Returns the name of the test database that it created.
create_test_db() tem o efeito colateral de modificar settings.DATABASE_NAME para bater com o nome do banco de dados de teste.
Destrói o banco de dados cujo nome está no parâmetro de configuração DATABASE_NAME e restaura o valor de DATABASE_NAME para o nome fornecido.
verbosity tem o mesmo comportamento que em run_tests().
Dec 26, 2011