Baixe o app para aproveitar ainda mais
Prévia do material em texto
Desenvolvimento Baseado em Testes BDD + Cucumber Eduardo Mendes edumendes@gmail.com @dudumendes Introdução Agenda BDD Cucumber @dudumendes BDD TDD Abordagem de desenvolvimento de software em que os testes direcionam a implementação do software TDD Favorece o design de software Expressa o comportamento do código Documenta o código @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 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 @dudumendes BDD Behaviour Driven Development @dudumendes BDD Behaviour Driven Design @dudumendes BDD “Behaviour-Driven Development is about implementing an application by describing its behaviour from the perspective of its stakeholders.” David Chemlimsky BDD “Desenvolvimento orientado a comportamento diz respeito a implementar uma aplicação pela descrição do seu comportamento a partir da perspectiva de seus stakeholders.” David Chemlimsky Entender o mundo a partir da visao do 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á envolvido no projeto 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 @dudumendes BDD Princípios Enough is enough Deliver stakeholder value It’s all behaviour @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 @dudumendes Ciclo do BDD red red greenrefactor green refactor Cenário Passo @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 @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 @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 @dudumendes BDD O Ciclo Desenvolvedores Automatizam cenários que orientam o desenvolvimento Descrevem comportamentos esperados Implementam os comportamentos Refatoram @dudumendes Processo do BDD @dudumendes Estórias no BDD 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 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 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 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] 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] Ciclo do BDD red red grerefactor gre refactor Cenário Passo @dudumendes Cucumber Cucumber Framework BDD Open Source baseado em RSpec Criado por Aslak Hellesøy Versão atual 1.2.1 Cucumber Características Implementação em Ruby Estórias de usuário baseadas em textos Suporte a Injeção de dependências 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 @dudumendes Cucumber em 03 passos Passo 1 - Escrever uma estória 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] 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] @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 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 @dudumendes cucumber @dudumendes continuação: snippets @dudumendes Resultado O cucumber encontrou a feature tentou executá-la mas não sabe como executá-la O cucumber sugeriu algumas dicas do código @dudumendes Cucumber em 03 passos Passo 2 - Criar o arquivo de passos @dudumendes Estórias no Cucumber arquivos em Ruby com sufixo “_steps” localização features/step_definitions execução comando cucumber @dudumendes 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 greeter_steps.rb @dudumendes Cucumber em 03 passos Passo 3 - Dar implementação às estórias @dudumendes 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 greeter_steps.rb @dudumendes @dudumendes class CucumberGreeter def greet "Hello Cucumber" end end Given /^a greeter$/ do @greeter = CucumberGreeter.new end When /^I sendit the greet message$/ do @message = @greeter.greet end Then /^I should see "(.*?)"$/ do |greeting| expect(@message).to eql greeting end @dudumendes Cucumber Passos Escrever uma estória Arquivo de texto de extensão .feature Recuperar os snippets Criar o arquivo de passos 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 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 (!-- ) @dudumendes Gherkin Feature: Scenario: Given When Then Internacionalização Inglês é o padrão # language: pt Aplicação Step definitions Features @dudumendes Gherkin Feature Scenario Scenario Outline Scenarios Given When Then And But | “” # @dudumendes Gherkin --i18n cucumber --i18n pt @dudumendes Exercício Criar uma versão em português para o Hello World 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 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:” @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 | @dudumendes O comando cucumber @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 @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 @dudumendes Organizando features @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 @dudumendes Organizando features features seguro medico dentario vida previdencia prbl vgbl cucumber features executa todos cucumber features/seguro apenas os de seguro cucumber features/seguro/medico somente os médicos @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 @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 @dudumendes Tags @aprovada @sprint_1 Funcionalidade: paciente solicita consulta @em_progresso Cenario: paciente seleciona horario disponivel cucumber --tags @em_progresso @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 @dudumendes Passo-a-passo @dudumendes Given When Then @dudumendesPassos Um passo candidato (StepCandidate) de uma feature deve estar associado a uma implementação de passo em um arquivo de passos feature_X.feature Cenario: Cenario A Dado um contexto Quando algo acontecer Então Alguma coisa acontece Arquivo de Passos cenario_a_steps.rb Dado um contexto Quando algo acontecer Então Alguma coisa acontece implementação implementação implementação Esta associação é feita através de expressões regulares @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 @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 @dudumendes Executa Cenário Lê 1.º passo Existe definição? Lê próximo passo Passo Indefinido Pendente?Exceção lançada? Existe outro passo Cenário Falho Executa código de definição do passo Não Sim Sim Não Cenário Pendente Sim Cenário Indefinido Sim Não Não Cenário Passou @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 @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 @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 @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 @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 @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 @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" @dudumendes @dudumendes Passos compostos @dudumendes Passos Compostos Em BDD, o autor de cenários deve possuir a liberdadede 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 @dudumendes Passos Compostos Passos compostos permitem criar grupos de execução de passos ligados a um único passo, o que pode ser muito útil e poderoso @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 @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 @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 @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" @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 @dudumendes Exercício Completar a feature da Transferência e fazer passar @dudumendes Hooks @dudumendes Hooks Before Executado antes de cada cenário After Executado depois de cada cenário AfterStep Executado após cada passo @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 @dudumendes Background Contexto, Cenario de Fundo Before, After, AfterStep Não possuem descrição na feature Background Similar ao Before Permite escrever passos na feature @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 @dudumendes Tabelas em passos @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 @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 @dudumendes Dado uma mao com as seguintes cartas | valor | naipe | | 2 | CORACAO | | 2 | ESPADAS | | 2 | PAUS | | 4 | OUROS | | A | CORACAO | [ { :valor => '2', :naipe => 'CORACAO'}, { :valor => '2', :naipe => 'ESPADAS'}, { :valor => '2', :naipe => 'PAUS'}, { :valor => '4', :naipe => 'OUROS'}, { :valor => 'A', :naipe => 'CORACAO'} ] @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 @dudumendes Exercício fazer passar a feature @dudumendes Exercício @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| @dudumendes Rails + cucumber + rspec pequena dose @dudumendes rails new showtime @dudumendes Gemfile group :development, :test do gem "rspec-rails" gem "webrat" end group :test do gem "cucumber-rails" gem ”database_cleaner” end @dudumendes bundle install rails generate rspec:install (dependencias do rspec) rails generate cucumber:install (dependencias do cucumber) rake db:migrate rake db:test:prepare rake spec rake cucumber @dudumendes 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" /features/horario.feature @dudumendes snippet @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 @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 @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 @dudumendes Validando o modelo @dudumendes rails generate model Email de:text para:text mensagem:text @dudumendes require 'spec_helper' describe Email do pending "add some examples to (or delete) #{__FILE__}" end @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 @dudumendes @dudumendesrequire '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 @dudumendes @dudumendes class Email < ActiveRecord::Base attr_accessible :mensagem, :para end app/models/email.rb @dudumendes class Email < ActiveRecord::Base attr_accessible :mensagem, :para validates_presence_of :para end app/models/email.rb @dudumendes # encoding: utf-8 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 @dudumendes @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 @dudumendes @dudumendes # encoding: utf-8 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" 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 @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 @dudumendesdescribe 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 @dudumendes class Email < ActiveRecord::Base attr_accessible :mensagem, :para validates_presence_of :para validates_presence_of :mensagem validates_format_of :para, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true end @dudumendes Bibliografia BEHAVIOUR-DRIVEN DEVELOPMENT. http:// behaviour-driven.org/. CHELIMSKY, David. The RSpec Book. PragBook, 2011. JBEHAVE. http://jbehave.org/reference/. PUGH, Ken. Lean-Agile Acceptance Test-Driven Development: better software through collaboration. Addison-Wesley, 2010.
Compartilhar