Testar o comportamento da sua aplicação, ao invés de pequenos módulos isolados, é uma grande prática no que diz respeito a escrita de testes que guiem o seu desenvolvimento.
Deixando a polêmica do "TDD is dead" de lado, criar cenários que garantem um determinado fluxo, além de servir como um excelente contrato à sua suite de aceitação, é uma ótima ferramenta para garantir que a integração back-end/front-end está funcionando de acordo com o esperado.
Devo ser sincero com você, caro leitor: Fazer BDD com Django (IMO) sempre foi uma dor de cabeça. Já utilizei algumas ferramentas, como unittest focado em comportamento, doctests, Lettuce, Pycurracy e até mesmo Jasmine... Nada pareceu ser "o certo a se fazer".
Recentemente esbarrei com um artigo ensinando a usar o Behave, um engine de testes BDD para Python... E foi aí que a minha opinião mudou.
O Behave é uma biblioteca Python que permite a escrita de specs em linguagem humana, e a execução dos cenários através de asserts em "linguagem de programação". Agnóstico de framework, é um engine promissor, fácil de usar, e que possui uma boa comunidade dando suporte.
Você pode escrever a integração do lib com o Django, como demonstrado na documentação oficial.
Como eu sou preguiçoso, prefiro utilizar o módulo behave-django
,
criado por Mitchel Cabuloy:
$ pip install behave-django
Não podemos esquecer de colocá-lo no INSTALLED_APPS
:
INSTALLED_APPS = (
...
'behave_django',
...
)
Agora basta criar uma estrutura na raíz do seu projeto (no mesmo nível do manage.py
), com a seguinte formação:
features/
steps/
steps.py
environment.py
funcionalidade.feature
Vamos para um exemplo mais prático: Eu quero que minha home exiba meu nome de usuário, caso eu esteja logado.
Começaremos pelo arquivo .feature
. Vou chamá-lo de features/home-logada.feature
:
Feature: Logged in page
Scenario: Access index page
Given an authenticated user
When I access the home page
Then I see my username printed
Já podemos executar o behave
através do manage.py
:
$ python manage.py behave
Você verá uma saída sinalizando que nosso cenário está montado, mas que ainda não há testes executando de fato.
O arquivo de teste pode ter o nome que você desejar, o Behave olhará
para cada ocorrência dentro de steps/
e coletará os passos que serão
usados para a execução das especificações.
Mas antes de escrevermos os testes, vamos falar de uma ferramenta essencial quando estamos fazendo testes de interface web com Python.
Diretamente da documentação oficial do Splinter:
Splinter is an open source tool for testing web applications using Python. It lets you automate browser actions, such as visiting URLs and interacting with their items.
A ferramenta fornece uma API única para diferentes ferramentas de testes de interface web, como Selenium, PhantomJS e zope.testbrowser.
Conseguimos instalá-la de forma muito fácil, através do comando pip
:
$ pip install splinter
É ele que nos permite, através de sua interface, escrever testes utilizando o driver do Firefox (por exemplo), e deixar as tasks executando em nosso servidor de integração contínua com um driver headless, como o PhantomJS.
Podemos utilizar o Splinter juntamente com a mecânica de teste do Behave.
A maneira mais fácil é através do esquema de configuração da suite,
criando o arquivo features/environment.py
, com o seguinte conteúdo:
from splinter import Browser
def before_all(context):
context.browser = Browser()
context.server_url = 'http://localhost:8000'
def after_all(context):
context.browser.quit()
Pronto! O contexto dos nossos testes tem a propriedade browser
, que é a instância do Splinter
para execução dos testes de interface.
As specs executarão com o Firefox, por padrão. Caso queira alterar o navegador,
basta especificá-lo na instância de Browser
:
context.browser = Browser('chrome')
Vamos escrever o código Python necessário para atender a seguinte condição:
Given an authenticated user
Para isso, criaremos um step específico para autenticação:
# features/steps/auth.py
from behave import given
from django.contrib.auth.models import User
@given('an authenticated user')
def given_auth_user(context):
User.objects.create_superuser(username='test', email='foo@bar', password='test')
br = context.browser
br.visit(context.base_url + '/admin/')
br.fill('username', 'test')
br.fill('password', 'test')
br.find_by_css('.submit-row input').first.click()
O código acima é simples e objetivo. Através do decorator @given
associamos um determinado
pedaço de código Python a um trecho das histórias escritas em .feature
.
Rodando o behave
, teremos uma saída similar a essa:
$ python manage.py behave
Creating test database for alias 'default'...
Feature: Logged in page # features/home-logada.feature:1
Scenario: Access index page # features/home-logada.feature:3
Given an authenticated user # features/steps/auth.py:5 0.646s
When I access the home page # None
Then I see my username printed # None
O motor de testes, além de identificar e executar o trecho de código necessário para contemplar uma expressão, exibe o tempo de execução da mesma.
Vamos para a parte onde o usuário interage com a aplicação.
# features/steps/actions.py
from behave import when
@when('I access the home page')
def access_the_home_page(context):
br = context.browser
br.visit(context.base_url + '/')
Aqui começa a ficar mais evidente o funcionamento de steps. Se amanhã
eu precisar de um novo cenário onde é necessário acessar a página inicial,
eu posso aproveitar o when
criado acima. O Behave fará isso automaticamente
para você... Portanto, organizar os steps de uma forma que eles agrupem
um determinado grupo de ações é uma sugestão interessante.
E para finalizar o nosso exemplo, vamos para o decorator de @then
,
que é o passo onde validamos o resultado dos acontecimentos disparados por @when
:
# features/steps/results.py
from behave import then
@then('I see my username printed')
def see_my_username(context):
br = context.browser
body = br.find_by_css('body')
assert 'Hello test!' in body.text
Pronto! Executamos nossas specs e temos o seguinte resultado:
$ python manage.py behave
Creating test database for alias 'default'...
Feature: Logged in page # features/home-logada.feature:1
Scenario: Access index page # features/home-logada.feature:3
Given an authenticated user # features/steps/auth.py:5 0.654s
When I access the home page # features/steps/actions.py:4 0.888s
Then I see my username printed # features/steps/results.py:4 0.075s
1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m1.617s
Destroying test database for alias 'default'...
Garantimos através dos cenários acima que, dado um usuário logado,
imprimiremos o seu username na rota http://localhost:8000/
.
O Behave é uma ótima ferramenta de BDD para Python. Sua integração com o Django e demais frameworks é relativamente simples, o que só aumenta a simpatia pela ferramenta.
Já sofri muito com escrita de testes de aceitação com Django. Não é um veredicto, mas até o momento o sentimento é extremamente positivo em relação ao casamento entre Behave e Django.
Até a próxima!