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................................................ 1 Introdução................................................ 2 Django Tests.............................................. 2 O Projeto............................................... 4 Primeiro Teste............................................. 5
CONTEÚDO 1 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.
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.
Introdução 4 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 <-- Testes `-- views.py -- pim/ -- pycache / -- init.py -- settings.py -- urls.py `-- wsgi.py `-- manage.py* 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
Introdução 5 $ 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:
Introdução 7 # 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. Criamos um teste; 2. Executamos para garantir que o teste falha; 3. Implementamos o código para que o teste passe; 4. 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.