Estruturando seu projeto com BDD e Cucumber

Publicado por Hézio Fernandes no dia qa

BDD-engineer

Todo bom programador que se preze, tem suas preocupações e caprichos em construir um código bom, limpo e que passe segurança ao deployar em produção. Sendo assim, práticas como design de código utilizando TDD (Test Driven Development) são sempre bem-vindas. Mas quem nunca teve dúvidas sobre o que testar? O que não testar? Como pensar primeiro nos testes?

BDD Behavior Driven Development

Dúvidas como estas fizeram o engenheiro de software Dan North desenvolver uma nova metodologia. Insatisfeito com a forma de desenvolver seus testes, buscou alternativas e decidiu focar no comportamento da funcionalidade ao invés de partes unitárias do sistema. Como resposta ao TDD, Dan North criou o BDD (Behavior Driven Development).

A colaboração entre áreas que convergem para o mesmo propósito, um software de qualidade baseado em requisitos, é um dos principais pilares do BDD.

A ideia, dentro de um time ágil, é que a pessoa responsável pelos objetivos do negócio (PM, Analista, Designer) contribua com o seu conhecimento sobre o produto e o time de desenvolvimento levante as dúvidas de engenharia. Fica a cargo da área de QA, com uma visão crítica a respeito de defeitos e falhas, informar quais cenários serão testados baseados nos critérios de aceites, considerando requisitos funcionais e não funcionais.

Aqui na Resultados Digitais a figura conhecida como Product Owner (PO) obteve uma mudança no seu papel e é chamado Product Manager (PM).

Como resultado da colaboração desses três personagens é possível obter uma documentação robusta e rica em detalhes que serve de guia para o desenvolvimento do produto que será entregue. Essa documentação é chamada Feature.

Existem vários benefícios de se trabalhar com esta metodologia juntamente com um framework que possibilite abstrair o BDD. Entre eles podemos citar:

  • Envolvimento dos interessados (stakeholders) no processo através de Outside-in Development
  • Todo time alinhado com a atividade que irá ser desenvolvida
  • Um artefato que será usado como guia contendo os critérios de aceite
  • Documentação viva refletindo os comportamentos atuais do sistema.
  • Testes automatizados baseados no próprio critério de aceite para prover um feedback rápido

Como o BDD é baseado nos princípios do TDD, vale lembrar que existe um ciclo semelhante no desenvolvimento guiado por comportamento.

BDD-Cycle

Primeiro escreva o teste de acordo com a Feature contendo o comportamento esperado. O teste deve falhar, porque ainda não existe o código.

Escreva o código de acordo com o comportamento esperado descrito na Feature.

Melhore seu código, refatore e aplique as melhores práticas. O teste deve continuar passando, garantindo que o comportamento não foi alterado.

Aqui na Resultados Digitais, os QAs, em conjunto com os desenvolvedores, escrevem inicialmente em Gherkin os cenários que serão desenvolvidos, os desenvolvedores codificam a solução e no final do ciclo de entrega os QAs automatizam os cenários especificados sempre que possível para aumentar a cobertura de testes de aceitação.

Existem algumas ferramentas que possibilitam a execução das especificações descritas no formato Feature. Entre elas posso citar Lettuce, JBehave, Spinach e Cucumber. Nesse post utilizei o cucumber, ferramenta que trabalho há alguns anos em consultoria e projetos paralelos.

Iniciando projeto com Cucumber

Para iniciar um projeto usando cucumber, é necessário configurar algumas gems no arquivo Gemfile, seguindo o exemplo abaixo:

Gemfile

1
2
3
4
group :test do
  gem 'cucumber-rails', : require => false
  gem 'capybara'
end

Após adicionar as gems, basta instalar as dependências especificadas no Gemfile, executando o comando:

1
bundle install

Depois, execute o comando para criação dos diretórios e estrutura do projeto:

1
 cucumber --init

A estrutura criada contém diretórios específicos para cada camada que será implementada. No diretório Feature deve ser criado um arquivo contendo a extensão .feature, onde serão escritos os critérios de aceite.

Features

A escrita das Features deve conter algumas palavras-chave que o cucumber utiliza para fazer o parse através do Gherkin. O gherkin utiliza uma linguagem semi-formal que interpreta a escrita e gera uma expressão regular capaz de referenciar cada frase da especificação descrita no arquivo .feature.

A Feature segue a estrutura abaixo:

Título
  • Descrição
  • Quem?
  • O que?
  • Para que?
Critérios de aceite ou cenários
  • Pré-condições
  • Gatilho
  • Resultado(s) esperado(s)
Palavras-chave:
  • As, In order to, I want to / I would like to
  • Como, Para, Eu quero / Eu gostaria
  • Given, When, Then, And, But, Background, Scenario, Scenario Outline
  • Dado que, Quando, Então, E, Mas, Contexto, Cenário, Esquema do Cenário

Exemplo de feature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#language:pt
@login
Funcionalidade: realizar login no sistema de gestão da resultados digitais
Para validar a autenticação no sistema de gestão da resultados digitais
Como usuário do sistema
Eu quero logar e validar as permissões de administrador, gestor e executivo.

Contexto: possibilidade de acessar o sistema
Dado que usuário possa acessar a tela de login do sistema

Esquema do Cenário: realizar login no sistema com usuários válidos
Quando informar um <Usuario> válido
E inserir uma <Senha> válida
Então o sistema deve permitir a autenticação do usuário
E o tipo de <Permissao> do usuário deve ser exibido

Exemplos:
| Usuario | Senha  | Permissao       |
| "jorge" | "0001" | "Administrador" |
| "sonia" | "0004" | "Gestor"        |
| "jose"  | "0006" | "Executivo"     |

O comando cucumber permite executar uma série de funções que podem ser informadas pelo comando “cucumber -help”, o argumento “-t” significa que iremos passar uma tag por parâmetro e “@login” é a tag que será executada, pois foi informada no início da escrita da feature.

Um conjunto de Tags também pode ser passado como argumento.

Ex:

1
 cucumber --tags @cenario1,@cenario2,@cenario3

Ou

1
 cucumber --t @cenario1,@cenario2,@cenario3

Step Definitions

Após a escrita da feature, deve ser executado o comando que irá ler o arquivo .feature e identificar os passos “steps” que devem ser implementados.

1
 cucumber -t @login

Executando o comando, o cucumber irá buscar no diretório os steps e os passos que fazem referência ao que foi escrito na feature. Caso eles não existam, serão exibidos no console, como o exemplo abaixo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
You can implement step definitions for undefined steps with these snippets:

Dado(/^que usuário possa acessar a tela de login do sistema$/) do
  pending # Write code here that turns the phrase above into concrete actions
end

Quando(/^informar um "([^"]*)" válido$/) do |arg1|
  pending # Write code here that turns the phrase above into concrete actions
end

Quando(/^inserir uma "([^"]*)" válida$/) do |arg1|
  pending # Write code here that turns the phrase above into concrete actions
end

Então(/^o sistema deve permitir a autenticação do usuário$/) do
  pending # Write code here that turns the phrase above into concrete actions
end

Então(/^o tipo de "([^"]*)" do usuário deve ser exibido$/) do |arg1|
  pending # Write code here that turns the phrase above into concrete actions
end

Depois dos Steps gerados, é hora de criar uma classe no diretório Steps Definitions e colar nela os passos gerados no console. Cada passo deverá ser um método desenvolvido, atendendo a descrição em forma de expressão regular. O desenvolvimento deve seguir step by step.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dado(/^que usuário possa acessar a tela de login do sistema$/) do
  visit system_rd_path
end

Quando(/^informar um "([^"]*)" válido$/) do |user|
  login_page.type_user(user)
end

Quando(/^inserir uma "([^"]*)" válida$/) do |password|
  login_page.type_password(password).click
end

Então(/^o sistema deve permitir a autenticação do usuário$/) do
  expect(page).to have_text I18n.t("system.index.title")
end

Então(/^o tipo de "([^"]*)" do usuário deve ser exibido$/) do |permission|
  expect(page).to have_text I18n.t(permission)
end

Hooks

Existem testes que necessitam de setup, onde são atribuídos trechos de código que precisam ser executado antes ou depois de iniciar a execução de um cenário. Esse setup deve ser implementado em uma classe que chamamos de hooks.

Abaixo temos uma implementação de uma classe hooks usada na feature desse exemplo:

1
2
3
4
5
6
7
8
Before '@login' do
  account = FactoryGirl.create(:account, uuid: 'kwfNGUf8G74454855548GH')
  @user = FactoryGirl.create(:confirmed_user_exemple, accounts: [account])
end

After '@login' do
  account = FactoryGirl.delete(:account, uuid: 'kwfNGUf8G74454855548GH')
end

Neste caso, implementamos no hooks um Before que irá criar uma conta específica para um determinado usuário antes que o primeiro passo mapeado no step seja executado. Isso ocorre, porque a anotação “@login” foi atribuída dentro do método Before.

Da mesma forma acontece com o método After, que só será executado após o último step implementado ser executado. Este método se encarrega de limpar a base, deletando a conta criada no Before.

Organizando Diretórios

Existem outras formas de organizar os diretórios dentro do projeto. Eu particularmente gosto de criar de manter Features, Step Definitions, Hooks, Page Objects e demais arquivos de configuração em diretórios bem definidos como no exemplo abaixo.

Structure

Para mapear diretórios diferentes na estrutura do seu projeto, basta setar no arquivo “cucumber.yml” o caminho definido que deseja, como no exemplo abaixo.

1
default: -r features/support/ -r features/step_definitions  -r features/specifications

Relatórios e Logs de execução

A geração de logs ou reports que o cucumber disponibiliza é um recurso muito interessante para evidenciar os testes que foram executados. As informações nos relatórios fornecem em detalhes as evidências sobre os cenários, contendo os registros de cada passo.

Os reports podem ser gerados em diferentes formatos durante a execução, de forma muito simples, utilizando os plugins formatadores: Pretty, HTML, JSON, Progress, Usage, JUnit, Rerun.

Para gerar o relatório, basta atribuir alguns comandos ao executar o cenário, como no exemplo abaixo:

1
cucumber -t @login --format html --out report

Neste relatório podemos extrair as seguintes informações sobre o status dos cenários executados:
- Aprovados - Falharam - Não executados - Número de cenários executados - Número de etapas executadas - Tempo total de execução de scripts.

Boas praticas

Existem boas práticas para o desenvolvimento orientado a comportamento BDD. As escritas, por exemplo, devem evitar referenciar elementos específicos da interface. O foco deve ser em o que a aplicação deve fazer e não em como deve fazer.

A escrita da feature pode ser feita pelo QA, mas a elaboração do documento não deve ser de responsabilidade exclusiva do mesmo. A colaboração do PM e Desenvolvedores são essenciais para o esclarecimento das funcionalidades que serão desenvolvidas.

Na escrita da feature, é importante ser específico em cada passo, evitando passar muitas variáveis, pois um step pode ser utilizado por outros cenários

Em um próximo post falaremos mais a fundo sobre as boas práticas na escrita e desenvolvimento dos cenários. Aguardem!

O cucumber já foi traduzido em mais de 60 idiomas e é recomendável que as Features sejam escritas na língua pátria dos interessado no produto (stakeholders).

Por último, deixo como recomendação o livro Specification by Example, que considero uma excelente leitura para quem tem dúvidas de como escrever de forma mais objetiva suas Features.

Referencias:

Quer se juntar ao time de produto da melhor empresa para se trabalhar em SC?

Hézio Fernandes

Hézio Fernandes

QA Automation Engineer / Consultant

Comentários