Harry J.W. Percival
Novatec
Authorized Portuguese translation of the English edition of Test-Driven Development with Python, 2nd Edition, ISBN 9781491958704 © 2017 Harry J. W. Percival. This translation is published and sold by permission of O'Reilly Media, Inc., the owner of all rights to publish and sell the same. Tradução em português autorizada da edição em inglês da obra Test-Driven Development with Python, 2nd Edition, ISBN 9781491958704 © 2017 Harry J. W. Percival. Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc., detentora de todos os direitos para publicação e venda desta obra. © Novatec Editora Ltda. [2017]. Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. É proibida a reprodução desta obra, mesmo parcial, por qualquer processo, sem prévia autorização, por escrito, do autor e da Editora. Editor: Rubens Prates ST20171128 Tradução: Lúcia A. Kinoshita Revisão gramatical: Tássia Carvalho Editoração eletrônica: Carolina Kuwabata ISBN: 978-85-7522-642-1 Histórico de impressões: Novembro/2017 Primeira edição Novatec Editora Ltda. Rua Luís Antônio dos Santos 110 02460-000 – São Paulo, SP – Brasil Tel.: +55 11 2959-6529 Email: novatec@novatec.com.br Site: www.novatec.com.br Twitter: twitter.com/novateceditora Facebook: facebook.com/novatec LinkedIn: linkedin.com/in/novatec
capítulo 1
Configurando o Django com um teste funcional
O TDD não é algo que surge naturalmente. É uma disciplina, como uma arte marcial, e, assim como em um filme de Kung Fu, você precisa de um mestre mal-humorado e prepotente para obrigá-lo a ter disciplina. O nosso mestre é o Testing Goat (bode dos testes).
Obedeça ao Testing Goat! Não faça nada até ter um teste O Testing Goat é o mascote não oficial de TDD da comunidade de testes de Python. Provavelmente ele apresenta diferentes significados para pessoas diferentes, mas, para mim, o Testing Goat é uma voz em minha mente que me mantém no Verdadeiro Caminho dos Testes – como um daqueles anjinhos ou diabinhos que aparecem em cima de seu ombro nos desenhos animados, mas com um conjunto muito específico de preocupações. Com este livro, espero colocar o Testing Goat em sua mente também. Decidimos construir um site, apesar de ainda não sabermos muito bem o que ele fará. Geralmente, o primeiro passo em um desenvolvimento web é ter seu framework web instalado e configurado. Faça download disso, instale aquilo, configure mais isso, execute o script…; porém, o TDD exige uma mentalidade diferente. Quando estiver fazendo TDD, tenha sempre o Testing Goat dentro de você – considerando como os caprinos são determinados – balindo: “Teste antes, teste antes!”. Em TDD o primeiro passo é sempre o mesmo: escreva um teste. Em primeiro lugar, escrevemos o teste, e depois o executamos e verificamos que ele falha conforme esperado. Somente então prosseguimos e desenvolvemos parte de
nossa aplicação. Repita isso para si mesmo como se estivesse balindo. É o que eu faço. Outro fato sobre os caprinos é que eles dão um passo de cada vez. Veja bem, é por isso que eles raramente caem das montanhas, independentemente do quão íngremes elas sejam, como podemos ver na Figura 1.1. 38
Capítulo 1 ■ Configurando o Django com um teste funcional
39
Figura 1.1 – Os caprinos são mais ágeis do que pensamos. (Fonte: Caitlin Stewart, no Flickr (http://www.flickr.com/photos/caitlinstewart/2846642630/))
Continuaremos com pequenos passinhos; usaremos o Django, um framework web popular para Python, a fim de construir nossa aplicação. Nossa primeira atitude será nos certificarmos de que o Django está instalado e pronto para que possamos trabalhar com ele. O modo como verificaremos isso é confirmando que podemos iniciar o servidor de desenvolvimento do Django e realmente o ver servindo uma página web no navegador em nosso microcomputador local. Usaremos a ferramenta de automação de navegação Selenium para isso. Crie um novo arquivo Python chamado functional_tests.py no local em que você quer manter o código de seu projeto e insira o código exibido a seguir. Se sentir vontade de dar alguns balidos enquanto faz isso, talvez ajude:
functional_tests.py from selenium import webdriver browser = webdriver.Firefox() browser.get('http://localhost:8000') assert 'Django' in browser.title
40
TDD com Python
Esse é o nosso primeiro FT (Functional Test, ou Teste Funcional); em breve, falarei mais sobre o que quero dizer com testes funcionais e como eles se comparam aos testes de unidade. Por enquanto, é suficiente garantir que compreendemos o que esse teste faz: • inicia um “webdriver” Selenium para apresentar uma verdadeira janela do navegador Firefox; • usa essa janela para abrir uma página web que esperamos que vá ser servida pelo microcomputador local; • verifica (fazendo uma asserção de teste) se a página tem a palavra “Django” no título. Vamos tentar executar este teste: $ python functional_tests.py File ".../selenium/webdriver/remote/webdriver.py", line 268, in get self.execute(Command.GET, {'url': url}) File ".../selenium/webdriver/remote/webdriver.py", line 256, in execute self.error_handler.check_response(response) File ".../selenium/webdriver/remote/errorhandler.py", line 194, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.WebDriverException: Message: Reached error page: abo ut:neterror?e=connectionFailure&u=http%3A//localhost%3A8000/[...]
Você deverá ver uma janela do navegador aparecer e tentar abrir localhost:8000; a página de erro “Unable to connect” (Incapaz de se conectar) será exibida. Se você retornar ao seu console, verá a mensagem de erro enorme e horrível informando-nos que o Selenium acessou uma página de erro. E então você provavelmente ficará irritado com o fato de a janela do Firefox ter sido deixada lá em seu desktop para que você faça a limpeza. Corrigiremos isso mais tarde! S e, em vez disso, você vir um erro de tentativa de importar o Selenium ou de encontrar o “geckodriver”, talvez seja necessário retornar e consultar novamente a seção “Pré-requisitos e suposições”.
Por enquanto, porém, temos um teste com falha, e isso significa que podemos começar a desenvolver nossa aplicação.
Capítulo 1 ■ Configurando o Django com um teste funcional
41
Adeus aos números romanos! Há tantas introduções ao TDD que usam números romanos como exemplo que passou a ser uma piada corrente – eu mesmo comecei a escrever um. Se estiver curioso, poderá vê-lo em minha página do GitHub (https://github.com/hjwp/tdd-roman-numeral-calculator/). Os números romanos, como exemplo, são, ao mesmo tempo, bons e ruins. É um bom problema “lúdico“, razoavelmente limitado em escopo, e podemos explicar o TDD muito bem com ele. O problema é que pode ser difícil relacioná-lo com o mundo real. É por isso que decidi usar a construção de uma aplicação web real, partindo do zero, como exemplo. Embora seja uma aplicação web simples, minha esperança é que isso facilite a transição para o próximo projeto de verdade.
Deixando o Django pronto para funcionar Como você certamente já leu a seção “Pré-requisitos e suposições” a essa altura, o Django deve estar instalado. O primeiro passo para que o Django esteja pronto e executando é criar um projeto, que será o contêiner principal para o nosso site. O Django disponibiliza uma pequena ferramenta de linha de comando para isso: $ django-admin.py startproject superlists
Esse comando criará uma pasta chamada superlists, e um conjunto de arquivos e subpastas contidos aí: . ├── functional_tests.py ├── geckodriver.log └── superlists ├── manage.py └── superlists ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py
42
TDD com Python
Sim, há uma pasta chamada superlists dentro de uma pasta chamada superlists. Embora um pouco confuso, é apenas um daqueles fatos para os quais há bons motivos quando olhamos para trás e vemos a história de Django. Por enquanto, o importante é saber que a pasta superlists/superlists serve para itens que se aplicam ao projeto como um todo – como settings.py, que é usado para armazenar informações sobre configurações globais para o site. Você também deve ter percebido a presença de manage.py. É o canivete suíço de Django, e uma das tarefas que ele consegue fazer é executar um servidor de desenvolvimento. Vamos testar isso agora. Execute um cd superlists para acessar a pasta superlists de nível mais alto (trabalharemos bastante a partir dessa pasta) e então execute: $ python manage.py runserver Performing system checks... System check identified no issues (0 silenced). You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. Django version 1.11.3, using settings 'superlists.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. seguro ignorar a mensagem sobre “unapplied migrations” (migrações não É aplicadas) por enquanto. Veremos as migrações no Capítulo 5.
Esse é o nosso servidor de desenvolvimento Django funcionando em nossa máquina. Deixe-o lá e abra outro shell de comandos. Nesse shell, podemos tentar executar nosso teste novamente (a partir da pasta em que começamos): $ python functional_tests.py $ omo acabamos de abrir uma nova janela do terminal, será necessário C ativar o seu virtualenv com workon superlists para que isso funcione.
Não há muita ação na linha de comando, mas você deve prestar atenção em dois pontos: em primeiro lugar, não houve nenhum AssertionError horrível e, em segundo, a janela do Firefox apresentada pelo Selenium continha uma página com aspecto diferente.
Capítulo 1 ■ Configurando o Django com um teste funcional
43
Bem, pode não parecer muito, mas esse foi o nosso primeiro teste em que passou! Viva! Se tudo parecer mágico demais, como se não fosse muito real, por que não damos uma olhada no servidor de desenvolvimento manualmente, abrindo um navegador web por conta própria e acessando http://localhost:8000? Você deverá ver algo semelhante à Figura 1.2. Se quiser, poderá encerrar agora o servidor de desenvolvimento e retornar ao shell original usando Ctrl-C.
Figura 1.2 – Funcionou!
Iniciando um repositório no Git Há uma última tarefa a ser feita antes de encerrarmos o capítulo: começar a fazer commit de nosso trabalho em um VCS (Version Control System, ou Sistema de Controle de Versões). Se você é um programador experiente, não precisará me ouvir fazendo pregações sobre controle de versões, mas se esse assunto lhe for uma novidade, por favor, acredite em mim quando digo que ter um VCS é obrigatório. Assim que seu projeto tiver mais que algumas semanas e algumas linhas de código, ter uma ferramenta disponível para rever versões antigas de código, reverter mudanças, explorar novas ideias com segurança ou mesmo ter o código como um backup… não
44
TDD com Python
tem preço. O TDD anda de mãos dadas com o controle de versões, portanto quero ter certeza de que vou explicar como esse sistema se encaixa no fluxo de trabalho. Então, vamos ao nosso primeiro commit! Apesar de já estarmos um pouco atrasados – que vergonha! Vamos usar o Git como nosso VCS, pois ele é o melhor. Começaremos passando functional_tests.py para a pasta superlists e executando git init para iniciar o repositório: $ ls superlists functional_tests.py geckodriver.log $ mv functional_tests.py superlists/ $ cd superlists $ git init . Initialised empty Git repository in /.../superlists/.git/
Nosso diretório de trabalho a partir de agora será a pasta superlists de nível mais alto A partir de agora, a pasta superlists de nível mais alto será o nosso diretório de trabalho. (Por questões de simplicidade, em minhas listagens de comandos, sempre a mostrarei como /…/superlists/, embora é provável que ela vá ser, na verdade, algo como / home/kind-reader-username/my-python-projects/superlists/.) Sempre que eu mostrar um comando a ser digitado, vou supor que estaremos nesse diretório. De modo semelhante, se eu mencionar um path para um arquivo, esse será relativo ao diretório de nível mais alto. Portanto, superlists/settings.py quer dizer o settings.py que está na pasta superlists no segundo nível. Claro como lama? Se estiver em dúvida, procure manage.py; você deverá estar no mesmo diretório em que está manage.py.
Vamos ver agora para quais arquivos queremos fazer commit: $ ls db.sqlite3 manage.py
superlists functional_tests.py
db.sqlite3 é um arquivo de banco de dados. Não queremos que ele esteja sujeito ao controle de versões. Anteriormente também vimos geckodriver.log, um arquivo de
log do Selenium, para o qual tampouco queremos controlar as mudanças. Adicionaremos ambos em um arquivo especial chamado .gitignore, que diz ao Git o que deve ser ignorado:
Capítulo 1 ■ Configurando o Django com um teste funcional
45
$ echo "db.sqlite3" >> .gitignore $ echo "geckodriver.log" >> .gitignore
A seguir podemos adicionar o restante do conteúdo da pasta atual, “.”: $ git add . $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new new new new new new new new new new new
file: file: file: file: file: file: file: file: file: file: file:
.gitignore functional_tests.py manage.py superlists/__init__.py superlists/__pycache__/__init__.cpython-36.pyc superlists/__pycache__/settings.cpython-36.pyc superlists/__pycache__/urls.cpython-36.pyc superlists/__pycache__/wsgi.cpython-36.pyc superlists/settings.py superlists/urls.py superlists/wsgi.py
Droga! Acabamos ficando com uma porção de arquivos .pyc; não tem sentido fazer commit deles. Vamos removê-los do Git e acrescentá-los ao .gitignore também: $ git rm -r --cached superlists/__pycache__ rm 'superlists/__pycache__/__init__.cpython-36.pyc' rm 'superlists/__pycache__/settings.cpython-36.pyc' rm 'superlists/__pycache__/urls.cpython-36.pyc' rm 'superlists/__pycache__/wsgi.cpython-36.pyc' $ echo "__pycache__" >> .gitignore $ echo "*.pyc" >> .gitignore
46
TDD com Python
Vamos ver agora qual é a nossa situação… (Você verá que uso bastante o comando git status – uso tanto que, com frequência, crio um alias git st para ele… mas não vou lhe dizer como fazer isso; deixarei que você mesmo descubra os segredos dos aliases de Git!): $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new new new new new new new
file: file: file: file: file: file: file:
.gitignore functional_tests.py manage.py superlists/__init__.py superlists/settings.py superlists/urls.py superlists/wsgi.py
Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified:
.gitignore
Parece bom – estamos prontos para fazer o nosso primeiro commit! $ git add .gitignore $ git commit
Quando digitar git commit, uma janela de editor será apresentada a você para escrever aí a sua mensagem de commit. A minha se parece com aquela exibida na Figura 1.3.1 1 O vi apareceu e você não tinha a mínima ideia do que fazer? Ou você viu uma mensagem sobre identidade de conta e git config --global user.username? Reveja a seção “Pré-requisitos e suposições”; há algumas instruções rápidas ali.
Capítulo 1 ■ Configurando o Django com um teste funcional
47
S e você quiser realmente entrar de cabeça no Git, essa também é a hora de saber como enviar seu trabalho para um serviço de hospedagem de VCS em nuvem, como o GitHub ou o Bitbucket. Eles serão convenientes se você achar que vai seguir este livro usando microcomputadores diferentes. Deixarei a seu encargo descobrir como eles funcionam; a documentação deles é excelente. Como alternativa, você poderá esperar até o Capítulo 9, quando usaremos um desses serviços para uma implantação.
Figura 1.3 – O primeiro commit no Git.
Isso é o que temos como lição sobre o VCS. Parabéns! Você escreveu um teste funcional usando o Selenium, além de ter conseguido instalar e executar o Django, usando TDD de forma comprovada, com um teste antes e a aprovação do Testing Goat (bode dos testes). Dê a si mesmo um bem merecido tapinha nas costas antes de passar para o Capítulo 2.