Django: Guia de testes

Transcrição

Django: Guia de testes
Django: Guia de testes
Osvaldo Santana Neto
©2014 Osvaldo Santana Neto
Tweet Sobre Esse Livro!
Por favor ajude Osvaldo Santana Neto a divulgar esse livro no Twitter!
O tweet sugerido para esse livro é:
Acabei de comprar o ebook ”Django: Guia de testes” do @osantana: http://j.mp/djteste
A hashtag sugerida para esse livro é #django-testes.
Descubra o que as outras pessoas estão falando sobre esse livro clicando nesse link para buscar a hashtag
no Twitter:
https://twitter.com/search?q=#django-testes
Para meus amores. Papai ama vocês.
Conteúdo
Créditos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introdução . . .
Django Tests .
O Projeto . .
Primeiro Teste
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
2
2
4
5
CONTEÚDO
Créditos
• Autor e Capa: Osvaldo Santana Neto
• Foto da Capa: Fabrizio Sciami
AVISO!
Esse livro ainda está em processo de escrita, portanto, as informações aqui podem estar
incorretas. Além disso a revisão de escrita (gramática, ortografia) será feita apenas na etapa
final. É bem provável que você encontre erros graves de português antes disso.
1
Introdução
Quando comecei a trabalhar com Django encontrei algumas dificuldades na criação de testes automatizados em meus projetos. Minhas dificuldades principais eram:
• Fazer com que os testes unitários não manipulassem o banco de dados;
• Escrever testes que executassem rápido;
• Testar só o meu código e não o framework.
Com o tempo eu me desapeguei de algumas crenças que tinha no desenvolvimento de testes unitários (ex.
testes unitários não devem interagir com o banco de dados) e aceitei o “Jeito Django” de testar.
Mas eu tive que aprender isso na marra porque não encontrava bons tutoriais oferecendo uma abordagem
mais prática e com exemplos baseados em aplicações de verdade. Só encontrava exemplos muito básicos
e completamente descolados do “mundo real”.
Vamos trabalhar com o Django 1.7 mas os exemplos devem funcionar sem maiores problemas com o
Django 1.6. Você perceberá que também uso Python 3 nos exemplos.
Django Tests
A distribuição padrão do Django trás quase tudo o que é necessário para testarmos nossas aplicações e
por isso vou focar nessas ferramentas.
Existe um bom conjunto de ferramentas e aplicações Django voltadas para o desenvolvimento de testes
no site Django Packages.
Organizando os testes
Um projeto Django ideal tem as suas funcionalidades separadas em várias aplicações (applications). O
modo padrão de organizar os testes de uma aplicação é criando módulos ou packages Python com nomes
seguindo o formato test*. Usaremos o formato: app/tests/test_*.py.
Dentro desses módulos você pode ter uma ou mais subclasses de um TestCase com métodos denominados
test_* onde implementaremos os cenários de testes que precisamos.
Executando os testes
Para executar os testes de um projeto é só usar o comando manage.py test:
Introdução
3
# Todos os testes do projeto
$ ./manage.py test
# Todos os testes da aplicação "reminder"
$ ./manage.py test reminder
# Todos os testes de um TestCase específico
$ ./manage.py test reminder.tests.test_views.ViewTestCase
# Apenas um teste
$ ./manage.py test reminder.tests.test_views.ViewTestCase.test_home
Dica 1
Durante o desenvolvimento de uma funcionalidade não é necessário executar o conjunto
completo de testes da sua aplicação o tempo todo. Execute apenas aqueles testes relacionados
diretamente com o que você está trabalhando. Deixe para executar todos os testes apenas
quando você terminar a sua sessão de trabalho e antes de fazer commit/push para se certificar
de que nada esteja quebrado.
Dica 2
Crie um ambiente de integração continua (Continuous Integration - CI) que execute todos os
testes do seu projeto sempre que alguém enviar código novo para o respositório de código.
Esse tipo de ferramenta é extremamente útil.
Tipos de TestCase
A distribuição padrão do Django disponibiliza vários tipos de classes “TestCase” mas as principais são:
django.test.TestCase
Essa classe funciona de forma análoga à classe unittest.TestCase da biblioteca padrão do Python mas
adiciona alguns facilitadores:
1. Cliente: uma instância de Client() no atributo self.client. Essa instância permite que a gente
simule a execução de requisições HTTP em nossa aplicação.
2. Transações: durante a execução dos testes o Django controla a execução das transações para
permitir que cada teste comece sempre em um cenário de banco de dados vazios.
django.test.LiveServerTestCase
Os conjuntos de testes que herdam dessa classe colocam o servidor de desenvolvimento do Django no ar
para permitir a execução de testes de interação com ferramentas para testes funcionais como Selenium ou
Splinter.
Esse tipo de teste é bem difícil de manter e, com o tempo, eles passam a ser algo que mais atrapalha do
que ajuda. Eu evito esse tipo de teste porque minhas aplicações geralmente não fazem uso muito intenso
de engenharia de frontend (HTML/CSS/JS). Se o seu projeto usa muito JS, por exemplo, esse tipo de teste
ganha mais importância.
4
Introdução
O Projeto
Para exemplificar o desenvolvimento guiado por testes vamor criar uma aplicação de agenda/calendário
(calendar) em um projeto de projeto de PIM (Personal Information Manager - Gerenciador de Informações
Pessoais). Essa aplicação deve ter as seguintes características:
•
•
•
•
•
•
•
Gerenciar Events (eventos) para determinado dia com hora de início e fim;
Um evento precisa começar e terminar no mesmo dia;
Se a hora de início e de término do evento não for informada ele terá duração de um dia inteiro;
Não podemos permitir a criação de mais de um evento para o mesmo horário;
O serviço precisa ser multiusuário (uma agenda por usuário);
Um usuário não poderá ver a agenda de outro;
O usuário receberá um email e um SMS (via Twilio) com a antecedência informada no evento.
Iniciando
Assumindo que você já tenha um ambiente virtual com Python 3 (venv) e com o Django 1.7 instalado.
Vamos iniciar o nosso projeto:
$
$
$
$
django-admin.py startproject pim
cd pim
./manage.py startapp cal
mkdir cal/templates/cal/
Nosso projeto deve ter uma estrutura parecida com essa:
pim/
|-- cal/
|
|-- migrations/
|
|
`-- __init__.py
|
|-- templates/
|
|
`-- cal/
|
|-- __init__.py
|
|-- admin.py
|
|-- models.py
|
|-- tests.py
|
`-- views.py
|-- pim/
|
|-- __pycache__/
|
|-- __init__.py
|
|-- settings.py
|
|-- urls.py
|
`-- wsgi.py
`-- manage.py*
<-- Testes
O comando ./manage.py startapp já cria um módulo tests.py mas eu prefiro organizar meus testes de
um modo que me permite separar um pouco mais as coisas
5
Introdução
$ mkdir cal/tests/
$ touch cal/tests/__init__.py
$ rm cal/tests.py
A aplicação cal vai ficar assim:
cal/
|-- migrations/
|
`-- __init__.py
|-- templates/
|
`-- cal/
|-- tests/
|
`-- __init__.py
|-- __init__.py
|-- admin.py
|-- models.py
`-- views.py
Primeiro Teste
Agora que temos a estrutura básica do nosso projeto vamos fazer um teste bem básico: verificar se a página
inicial está funcionando.
Vamos criar o primeiro teste:
# cal/tests/test_views.py
from django.test import TestCase
class ViewTestCase(TestCase):
def test_home(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
# 200 OK
E adicionaremos a aplicação cal na nossa lista de aplicações instaladas (INSTALLED_APPS) no arquivo
pim/settings.py:
# pim/settings.py
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
Introdução
6
# Project applications
'cal',
)
Agora estamos prontos para executar o nosso teste:
$ ./manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_home (cal.tests.test_views.ViewTestCase)
---------------------------------------------------------------------Traceback (most recent call last):
File "./pim/cal/tests/test_views.py", line 8, in test_home
self.assertEqual(response.status_code, 200) # 200 OK
AssertionError: 404 != 200
---------------------------------------------------------------------Ran 1 test in 0.035s
FAILED (failures=1)
Destroying test database for alias 'default'...
E como já era esperado o nosso teste está falhando, afinal, ainda não criamos a view nem roteamos a URL
para nossa página home. Vamos implementar a nossa view:
# cal/views.py
from django.shortcuts import render
def home(request):
return render(request, "cal/home.html")
Criar um template para ser renderizado:
<!-- cal/templates/cal/home.html -->
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello Django Tests!</h1>
</body>
</html>
E, finalmente, rotear uma URL apontando para a nossa view:
7
Introdução
# pim/urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^$', 'cal.views.home', name='home'),
url(r'^admin/', include(admin.site.urls)),
)
# / -> home
Pronto. Agora podemos executar o nosso teste novamente:
$ ./manage.py test
Creating test database for alias 'default'...
.
---------------------------------------------------------------------Ran 1 test in 0.011s
OK
Destroying test database for alias 'default'...
E tudo passou perfeitamente. Estamos prontos para continuar.
Conclusão
Quando a gente trabalha com Test-Driven Development esses passos se repetem o tempo todo:
1.
2.
3.
4.
Criamos um teste;
Executamos para garantir que o teste falha;
Implementamos o código para que o teste passe;
Refatoramos o código garantindo que os testes continuem passando.
Dica
Se você criou um teste novo e ele passa de primeira desconfie que ele está testando algo que
já foi testado ou que a implementação do teste está incorreta.

Documentos relacionados

Komodo Edit – Customizando e utilizando com

Komodo Edit – Customizando e utilizando com Se não der pra ler, dentro do “Run” que é onde vai o programa, está digitado o seguinte comando: django-admin.py startproject %(ask:Digite o nome do Projeto:) Marque a opção “Add to Toolbox” Em Adv...

Leia mais

Django - Elton Minetto

Django - Elton Minetto Deu para perceber que cada modelo é uma classe em Python, o método __str__ foi definido para que o nome do objeto seja retornado. A classe interna chamada Admin que foi definida em cada classe sign...

Leia mais