Este documento apresenta uma introdução ao desenvolvimento baseado em testes (TDD) utilizando a abordagem Behavior Driven Development (BDD) e a ferramenta Cucumber. O documento discute os princípios e o ciclo do BDD, como escrever estórias e cenários no Cucumber, e como implementar os passos no Cucumber.
5. TDD
Abordagem de desenvolvimento de software em
que os testes direcionam a implementação do
software
6. TDD
Favorece o design de software
Expressa o comportamento do código
Documenta o código
7. @dudumendes
Testes unitários
Uma forma de aplicar o TDD
Traduzem as expectativas dos desenvolvedores
sobre o comportamento do código
Fragilidade: Muito acoplado à implementação
Não traduz as expectativas dos usuários
8. Expectativas dos usuários
Casos de Uso
Estórias de Usuário
Ambas não possuem mecanismos de validação que
traduzam as expectativas dos usuários
12. Entender o mundo a partir da visao do
BDD
STK_HD
Para entregar COISAS UTEIS
Entender o seu domínio
Seus desafios e oportunidades
As palavras que ele usa para descrever o
comportamento que ele quer da
aplicação
Mais que da visão de um stakeholder
Do ponto de vista de qualquer que está
“Desenvolvimento orientado a comportamento diz
envolvido no projeto
respeito a implementar uma aplicação pela descrição
do seu comportamento a partir da perspectiva de seus
stakeholders.”
David Chemlimsky
13. Behaviour Driven Development
Uma abordagem no estilo TDD
Documentação executável
Melhora a comunicação dos times
Esclarece os mal-entendimentos entre clientes,
especialistas de domínio, desenvolvedores
Pode ser utilizada por todos os envolvidos no
projeto
15. @dudumendes
BDD
Princípios
O bastante é o bastante
Trabalhar para alcançar as expectativas dos stakeholders, mas
evitar fazer mais do que se é necessário fazer
Entregar valor ao stakeholder
Se você está fazendo algo que não entrega valor ou não aumenta
sua habilidade de entrega de valor, pare e faça outra coisa
Tudo é sobre comportamento
Assim como podemos descrever o comportamento a partir da
perspectivas dos stakeholders, também podemos descrever o
comportamento de um código a partir de outro código que o utiliza
16. @dudumendes
red
Ciclo do BDD
red
refactor
green
refactor
Passo
green
Cenário
17. @dudumendes
BDD
O Ciclo
Stakeholder e analista discutem os requisitos
os requisitos são organizados em funcionalidades
(features)
podem ser quebradas em estórias
fazem sentido para o stakeholder
18. @dudumendes
BDD
O Ciclo
Stakeholder, analista e testador determinam os
escopos das estórias
o analista pensa na funcionalidade de forma geral
o testador pensa em cenários concretos, com
valores de entradas e saída
19. @dudumendes
BDD
O Ciclo
Cenários prioritários são identificados
Stakeholder especifica exatamente o que quer
entregue
Desenvolvedores implementam o bastante para
satisfazer os cenários e nada mais
20. @dudumendes
BDD
O Ciclo
Desenvolvedores
Automatizam cenários que orientam o
desenvolvimento
Descrevem comportamentos esperados
Implementam os comportamentos
Refatoram
23. BDD
Estórias e Comportamento
Estórias
Correspondem às Estórias de Usuário
Expressam o comportamento das explicações em
alto nível
Necessidade de frameworks de estórias
Cucumber, JBehave
24. BDD
Estórias e Comportamento
Comportamento (ou Spec)
Correspondem às expectativas em nível de
classe
Expressam o comportamento a nível de serviço/
componente
25. Estórias e Comportamento
Estórias de Usuário
Formado por um conjunto de Cenários
(Scenarios)
critérios de aceitação
Cada cenários possui
“entradas”, eventos e “resultados”
Utilizados em projetos ágeis
Behaviour
Expressado como métodos de testes
Define o que aplicação deve e não deve fazer
26. Estrutura de uma estória
Connextra Format
Título
Narrativa
As a [algum_papel]
I want [alguma_necessidade]
So that [beneficio/valor_da_caracteristica]
Critérios de aceitação (cenários)
Given [alguma(s)_condicao(oes)]
When [evento_ocorrer]
Then [certifico_algum_resultado]
27. Estrutura de uma estória
Título
Narrativa
Como um [algum_papel]
Eu quero [alguma_necessidade]
Para que [beneficio/valor_da_caracteristica]
Critérios de aceitação (cenários)
Dado [alguma(s)_condicao(oes)_entrada]
Quando [evento_ocorrer]
Então [certifico_algum_resultado]
32. Cucumber
em 03 passos
Escrever uma estória e executar a estória (.feature)
obter os snippets com os passos do teste
Criar o arquivos de passos a partir dos snippets
Dar implementação aos passos
34. Estrutura de uma estória
Narrativa
As a [algum_papel]
I want [alguma_necessidade]
So that [beneficio/valor_da_caracteristica]
Cenário
Given [alguma(s)_condicao(oes)]
When [evento_ocorrer]
Then [certifico_algum_resultado]
35. Estrutura de uma estória
Narrative:
In order to [beneficio/valor_da_caracteristica]
As a [algum_papel]
I want to [alguma_necessidade]
Scenario: Nome do Cenário
Given [alguma(s)_condicao(oes)]
When [evento_ocorrer]
Then [certifico_algum_resultado]
36. @dudumendes
Estórias no Cucumber
arquivos
de texto com extensão “.feature”
localização
diretório features
o comando procura por um diretório “features”
execução
comando cucumber
busca o diretório
37. Cucumber em 03 passos
Passo 1 - Escrever uma estória
Feature: greeter says hello
In order to test Cucumber
As a developer
I want a greeter to say hello
Scenario: greeter says hello
Given a greeter
When I send it the greet message
Then I should see "Hello Cucumber"
gretter_say_hello.feature
43. @dudumendes
greeter_steps.rb
Given /^a greeter$/ do
pending # express the regexp above with the code you wish you had
end
When /^I send it the greet message$/ do
pending # express the regexp above with the code you wish you had
end
Then /^I should see "(.*?)"$/ do |arg1|
pending # express the regexp above with the code you wish you had
end
45. @dudumendes
greeter_steps.rb
Given /^a greeter$/ do
@greeter = CucumberGreeter.new
end
When /^I send it the greet message$/ do
@message = @greeter.greet
end
Then /^I should see "(.*?)"$/ do |greeting|
@message.should == greeting
end
47. @dudumendes
class CucumberGreeter
def greet
"Hello Cucumber"
end
end
Given /^a greeter$/ do
@greeter = CucumberGreeter.new
end
When /^I send it the greet message$/ do
@message = @greeter.greet
end
Then /^I should see "(.*?)"$/ do |greeting|
expect(@message).to eql greeting
end
49. Cucumber Passos
Escrever uma estória
Arquivo de texto de extensão .feature
Recuperar os snippets
Criar o arquivo de passos
50. Estórias no Cucumber
Estórias em arquivos de textos
01 Narrativa (Narrative)
* Cenários (Scenarios)
Narrativa
Opcional
As a, In order to, I want to
51. Cenários no Cucumber
Cenários consistem em:
Título
Passos:
Given, When, Then
And
é possível colocar then antes do when
Cenários podem depender de outros
Comentários (!-- )
52. @dudumendes
Gherkin
Features
Step definitions
Feature: Aplicação
Scenario:
Given When Then
Internacionalização
Inglês é o padrão
# language: pt
56. Esquema de Cenários
Scenario Outline
É comum em testes de aceitação definir-se exemplos
de cenários reais com valores para verificar o estado
de pronto da aplicação
Neste caso é possível se utilizar esquemas de cenários
57. Esquemas de cenários
Nos cenários os parâmetros devem ser envolvidos
com sinais “<” e “>”
Eles devem ser declarados como esquemas de
cenários
Após um cenário informam-se os valores válidos para
os parâmetros em um tabela determinada por
“Cenários:”
58. @dudumendes
# language: pt
Funcionalidade: Depositar Dinheiro
Esquema do Cenário: Depositar Dinheiro
Dado um cliente especial com saldo atual de <saldo_inicial> reais
Quando ele realizar um deposito no valor <deposito> reais
Então o deposito deve ser realizado
E o saldo da conta atualizado para <saldo_final> reais
Cenarios: valores possiveis
| saldo_inicial | deposito | saldo_final |
| 200 | 100 | 300 |
| 200 | 100 | 300 |
| 200 | 100 | 300 |
| 200 | 100 | 300 |
60. @dudumendes
Organizando features
Comando cucumber sem opções
serão procurados todos os arquivos .rb e .feature
abaixo do diretório features
gera snippets para features indefinidas
cada Cenário e Passo tem comentários no final da
linha
localização do cenário
nome do arquivo e número da linha
61. @dudumendes
# language: pt
Funcionalidade: Futuro hospede reserva um quarto
A fim de proporcionar mais comodidade
Como dono do hotel
Eu gostaria que os futuros hospedes
reservassem quartos pela internet
Cenario: Reserva com sucesso
Dado um hotel com "10" quartos e "0" reservas
63. @dudumendes
Organizando features
Comando cucumber sem opções
serão procurados todos os arquivos .rb e .feature
Para projetos pequenos
caminho mais simples é organizar os arquivos dentro do
diretório features
Para projetos maiores
crie subdiretorios para cada feature
vários arquivos em cada diretório com um subconjunto de
ceários coesos
64. @dudumendes
Organizando features
features
seguro cucumber features
medico executa todos
dentario cucumber features/seguro
vida apenas os de seguro
previdencia cucumber features/seguro/medico
prbl somente os médicos
vgbl
65. @dudumendes
A vida de um cenário
No processo de software
Cada cenário de uma funcionalidade precisa ser descrito e
aprovado
Durente a especificação, os cenários podem estar
pendentes de aprovação
em progresso
estados mistos em uma mesma feature
pode ser necessário testar um subjconjunto da suites de testes
66. @dudumendes
Tags para features/scenarios
É como uma annotation
pode ser utilizada para marcar features e/ou scenarios
scenarios herdam as tags de suas features
podem ser utilizadas para
68. @dudumendes
Tags
cucumber --tags @cuc, @umber
cucumber --tags @cuc || @umber
executa todos os cenário com @cuc OU @umber
cucumber --tags @cuc --tags @umber
cucumber --tags @cuc && @umber
executa todos os cenário com @cuc E @umber
cucumber --tags ~@umber
cucumber --tags !@umber
executa todos os cenário sem @umber
71. Passos
@dudumendes
Um passo candidato (StepCandidate) de uma
feature deve estar associado a uma implementação
de passo em um arquivo de passos
Arquivo de Passos
cenario_a_steps.rb
feature_X.feature
Dado um contexto
Cenario: Cenario A
implementação
Dado um contexto
Quando algo acontecer
Quando algo acontecer
implementação
Então Alguma coisa acontece
Então Alguma coisa acontece
implementação
Esta associação é feita através de expressões regulares
72. @dudumendes
Passos
Um passo candidato (StepCandidate) de uma
feature deve estar associado a uma implementação
de passo em um arquivo de passos
Caso esteja: passed
Caso contrário: undefined
73. @dudumendes
Definição de passos
Criação do arquivo de feature
Quando você cria uma feature
executa o cucumber
cenários indefinidos
passos indefinidos
snippets de código
74. @dudumendes
Executa
Cenário
Existe Não
Passo Cenário
Lê 1.º passo definição? Indefinido Indefinido
Sim
Executa código Exceção Sim
Lê próximo de definição do Pendente?
lançada?
passo passo
Não
Não
Sim Existe Sim
outro passo
Não
Cenário Cenário
Falho Pendente
Cenário
Passou
75. @dudumendes
# language: pt
Funcionalidade: Futuro hospede reserva um quarto
A fim de proporcionar mais comodidade
Como dono do hotel
Eu gostaria que os futuros hospedes reservassem quartos pela internet
Cenario: Reservar com sucesso
Dado um hotel com "10" quartos e "0" reservas
76. @dudumendes
Definição de passos
Criação do arquivo de passos
Cria-se o arquivo de passos
Dado /^um hotel com "(.*?)" quartos e "(.*?)"
reservas$/ do |total_de_quartos, total_de_reservas|
end
77. @dudumendes
Given / When / Then
O formato Connextra
Utilizados para descrever os passos de um teste de
aceitação
servem para descrever o cenário de uma vida real
expressam comportamento esperado
78. @dudumendes
Given
Dado
Indica algo que se aceita como verdadeiro e certo em um cenário
Dado que tenho R$ 20 em minha conta
Dado que a Terra é redonda
Dado que hoje é sábado
É uma setença que descreve um contexto para os eventos e
saídas que serão exercitados nos cenários
Não são pré-condições
79. @dudumendes
When
Quando
Indica o evento que ocorre naquele cenário
Quando eu saco R$ 10
Quando eu pulo da estratosfera
Melhor que se tenha um evento por cenário
melhor descrição da intenção de cada cenário
falhas não serão ocultadas quando mais que um evento
for descrito
80. @dudumendes
Then
Então
Indica uma saída esperada
Então eu devo ter R$ 5 no final
Então eu devo pular antes de sair da atmosfera
Pode haver mais que uma saída por cenário
mas devem ser coesas
81. @dudumendes
And / But
E / Mas
Podem ser utilizados para complementar a descrição
do Given e do Then
Cenario: Reserva com sucesso
Dado um hotel com "10" quartos
E com "0" reservas
Quando um futuro hospede reservar "1" quarto
Entao reservas será "1"
E o número dos quartos será "9"
84. @dudumendes
Passos Compostos
Em BDD, o autor de cenários deve possuir a liberdade
de aprofundar o foco de um determinado cenário
esta situação pode ultrapassar o limite de um
cenário inicial
um passo pode depender de passos utilizados em
outros cenários
Neste contexto, os cenários compostos são úteis
86. @dudumendes
Funcionalidade: Logar na aplicacao
Cenário: Usuario existe
Dado que o usuario "dudumendes" existe
Dado que eu informei o login "dudumendes"
Dado /^que o usuario "(.*?)" existe$/ do |nome|
# ...
end
Dado /^que eu informei o login "(.*?)"$/ do |nome|
# ...
end
87. @dudumendes
Cenário: Usuario existe
Dado que o usuario "dudumendes" existe
Dado que eu informei o login "dudumendes"
Dado que "dudumendes" está logado
Dado /^que o usuario "(.*?)" existe$/ do |nome|
# ...
end
Dado /^que eu informei o login "(.*?)"$/ do |nome|
# ...
end
Dado /^que (.*?) está logado$/ do |nome|
step "que o usuario #{nome} existe"
step "que eu informei o login #{nome}"
end
88. @dudumendes
Funcionalidade: Transferir Dinheiro
Cenário: Transferir Dinheiro
Quando eu seleciono "conta corrente" como conta de origem
E eu seleciono "poupanca" como conta de destino
E eu informo que a quantidade é 20
E solicito executar
Quando /^eu seleciono "(.*?)" como conta de origem$/ do |origem|
end
Quando /^eu seleciono "(.*?)" como conta de destino$/ do |destino|
end
Quando /^eu informo que a quantidade é (d+)$/ do |valor|
end
Quando /^solicito executar$/ do
end
89. @dudumendes
Funcionalidade: Transferir Dinheiro
Cenário: Transferir Dinheiro
Quando eu seleciono "conta corrente" como conta de origem
E eu seleciono "poupanca" como conta de destino
E eu informo que a quantidade é 20
E solicito executar
Cenário: Transferir Dinheiro Resumido
Quando eu transfiro 20 da "conta corrente" para a "poupanca"
90. @dudumendes
Quando /^eu transfiro (d+) da (.*?) para a (.*?)$/ do |valor, origem, destino|
step "eu seleciono #{origem} como conta de origem"
step "eu seleciono #{destino} como conta de destino"
step "eu informo que a quantidade é #{valor}"
step "solicito executar"
end
94. @dudumendes
Before do
puts "Isto executa antes de cada cenário"
end
After do
puts "Isto executa depois de cada cenário"
end
AfterStep do
puts "Isto executa depois de cada passo"
end
Before(“@cuc”) do
puts "Isto executa antes de cada cenário com @cuc"
end
96. @dudumendes
Funcionalidade: Convidar amigos
Contexto: Usuario logado
Dado que "dudumendes" esta logado
Cenário: Convidar alguem que já é amigo
Cenário: Convidar alguem que ainda não é amigo
98. @dudumendes
Funcionalidade: Baralho
Cenário: tres do mesmo tipo ganha de dois pares
Dado uma mao com as seguintes cartas
| valor | naipe |
| 2 | CORACAO |
| 2 | ESPADAS |
| 2 | PAUS |
| 4 | OUROS |
| A | CORACAO |
E outra mao com as seguintes cartas
| valor | naipe |
| 2 | CORACAO |
| 2 | ESPADAS |
| 4 | PAUS |
| 4 | OUROS |
| A | CORACAO |
Entao a primeira mao deve ganhar da segunda mao
99. @dudumendes
Dado /^uma mao com as seguintes cartas$/ do |table|
# table is a Cucumber::Ast::Table
pending # express the regexp above with the code you wish you had
end
Dado /^outra mao com as seguintes cartas$/ do |table|
# table is a Cucumber::Ast::Table
pending # express the regexp above with the code you wish you had
end
Entao /^a primeira mao deve ganhar da segunda mao$/ do
pending # express the regexp above with the code you wish you had
101. @dudumendes
Dado /^uma mao com as seguintes cartas$/ do |cartas|
cartas.hashes.each {|hash|
@primeira_mao << Carta.new(hash)
}
end
Dado /^outra mao com as seguintes cartas$/ do |cartas|
cartas.hashes.each {|hash|
@segunda_mao << Carta.new(hash)
}
end
104. @dudumendes
Criar a feature e fazer passar
Dados
os
negociantes:
|nome|rank|
|Larry|Estagio
3|
|Moe|Estagio
1|
|Curly|Estagio
2|
Quando
os
negociantes
são
ordenados
pelo
nome
Então
os
comerciantes
devem
vir
na
seguinte
ordem:
|nome|rank|
|Curly|Estagio
2|
|Larry|Estagio
3|
|Moe|Estagio
1|
109. @dudumendes
/features/horario.feature
Funcionalidade: Descrições
Como um frequentador de cinema
Eu quero ver horarios precisos e concisos
Para que eu possa encontrar filmes que se encaixem no meu horario
Cenário: Exibir minutos para horarios que não terminam em 00
Dado um filme
Quando eu configuro o horario para "2012-10-10" às "2:15pm"
Então o horario deve ser "October 10, 2012 2:15pm"
111. @dudumendes
features/step_definitions/horario_steps.rb
# encoding: utf-8
# language: pt
Dado /^um filme$/ do
@filme = Filme.create!
end
Quando /^eu configuro o horario para "(.*?)" às "(.*?)"$/ do |data, hora|
@filme.update_attribute(:horario_data, Date.parse(data))
@filme.update_attribute(:horario_hora, hora)
end
Então /^o horario deve ser "([^"]*)"$/ do |horario|
expect(@filme.horario).to eql horario
end
112. @dudumendes
Criar o Model Filme
rails g model filme horario_data:date horario_hora:time
Além do modelo criará o spec spec/model/filme_spec.rb
rake db:migrate
rake db:test:prepare
113. @dudumendes
app/models/filme.rb
class Filme < ActiveRecord::Base
attr_accessible :horario_data, :horario_hora
def horario
"#{data_formatada} #{hora_formatada}"
end
def data_formatada
horario_data.strftime("%B %d, %Y")
end
def hora_formatada
horario_hora.strftime("%l:%M%p").strip.downcase
end
end
117. @dudumendes
require 'spec_helper'
describe Email do
context "validações:" do
it "para é obrigatório"
it "para é válido com email válido"
it "para é inválido com email inválido"
it "mensagem é obrigatória"
end
end
119. @dudumendes
require 'spec_helper'
describe Email do
context "validações:" do
it "para é obrigatório" do
email = Email.create
expect(email).to have(1).error_on(:para)
end
it "para é válido com email válido"
it "para é inválido com email inválido"
it "mensagem é obrigatória"
end
end
123. @dudumendes
require 'spec_helper'
describe Email do
context "validações:" do
it "de é obrigatório" do
email = Email.create
expect(email).to have(1).error_on(:de)
end
it "para é obrigatório"
it "mensagem é obrigatória"
it "de deve ser um email" do
email = Email.create(:de => "de@email.com")
expect(email).to have(:no).error_on(:de)
end
it "para deve ser um email"
end
end
125. @dudumendes
describe Email do
context "validações:" do
it "para é obrigatório" do
email = Email.create
expect(email).to have(1).error_on(:para)
end
it "para é válido com email válido" do
email = Email.create(:para => "para@email.com")
expect(email).to have(:no).error_on(:para)
end
it "para é inválido com email inválido"
it "mensagem é obrigatória"
end
end
127. require 'spec_helper' @dudumendes
describe Email do
context "validações:" do
it "para é obrigatório" do
email = Email.create
expect(email).to have(1).error_on(:para)
end
it "para é válido com email válido" do
email = Email.create(:para => "para@email.com")
expect(email).to have(:no).error_on(:para)
end
it "para é inválido com email inválido" do
email = Email.create(:para => "invalido")
expect(email).to have(1).error_on(:para)
end
it "mensagem é obrigatória"
end
end
128. @dudumendes
class Email < ActiveRecord::Base
attr_accessible :mensagem, :para
validates_presence_of :para
validates_format_of :para,
:with => /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})z/i,
:allow_blank => true
end
129. @dudumendes
describe Email do
context "validações:" do
it "para é obrigatório" do
email = Email.create
expect(email).to have(1).error_on(:para)
end
it "para é válido com email válido" do
email = Email.create(:para => "para@email.com")
expect(email).to have(:no).error_on(:para)
end
it "para é inválido com email inválido" do
email = Email.create(:para => "invalido")
expect(email).to have(1).error_on(:para)
end
it "mensagem é obrigatória" do
email = Email.create
expect(email).to have(1).error_on(:mensagem)
end
end
end