Esta é uma tradução do artigo Introducing BDD, escrito por Dan North e traduzido por Carlos Vilella e Danilo Sato
Eu estava com um problema. Ao usar e ensinar práticas ágeis como Test-Driven Development (Desenvolvimento Orientado por Testes ou TDD) em diferentes projetos, acabava topando sempre com as mesmas confusões e desentendimentos. Programadores queriam saber por onde começar, o que testar e o que não testar, quanto testar de cada vez, como dar nomes aos testes e como entender a causa de uma falha.
Quanto mais eu me aprofundei em TDD, mais eu senti que minha própria jornada não tinha sido um processo gradual e contÃnuo (“põe a cera, tira a cera”), mas sim uma série enorme de becos sem saÃda. Me lembro de pensar muito mais em “se pelo menos alguém tivesse me dito isso!†do que “uau, uma porta se abriuâ€. Decidi que tinha que ser possÃvel apresentar TDD de uma maneira que mostra diretamente as qualidades da técnica, evitando todas as armadilhas.
Minha resposta é o Behaviour-Driven Development (Desenvolvimento Orientado por Comportamentos ou BDD). Esta técnica evoluiu a partir de práticas ágeis estabelecidas e seu intuito é torna-las mais acessÃveis e efetivas para times que estão dando seus primeiros passos no desenvolvimento ágil de software. Com o tempo, BDD se amplout o cenário mais abrangente de análise ágil e testes de aceitação automatizados.
Nomes de métodos devem ser frases
Meu primeiro “aha!†aconteceu quando me mostraram um utilitário extremamente simples, agiledox, escrito pelo meu colega Chris Stevenson. O agiledox lê uma classe de testes do JUnit e exibe o nome dos métodos em forma de frases, então um conjunto de testes assim:
public class CustomerLookupTest extends TestCase { public void testFindsCustomerById() { ... } public void testFailsForDuplicateCustomers() { ... } } |
fica mais ou menos assim:
CustomerLookup - finds customer by id - fails for duplicate customers - ...
A palavra “test†é removida tanto do nome da classe quanto do nome dos métodos e o texto do nome do método é convertido de camel-case para uma frase comum. Isso é tudo que o agiledox faz, mas seu efeito é impressionante.
Os desenvolvedores descobriram que ele poderia ajudá-los gerando pelo menos uma parte da documentação e começaram a nomear os métodos de teste com frases que faziam sentido. Melhor ainda, eles descobriram que quando escreviam nomes de métodos na linguagem do cliente, os documentos gerados faziam sentido para o cliente, analistas e testadores.
Um modelo de frase simples mantém o foco do teste
Então eu descobri a convenção de usar a palavra “deveria” (“should”) nos métodos de teste. Este modelo – a classe “deveria” fazer alguma coisa – implica que você só pode definir um teste para uma classe especÃfica. Isso mantém seu foco. Se você estiver escrevendo um teste cujo nome não se encaixa no modelo, então pode ser que o comportamento que você está tentando testar pertence a outro lugar.
Por exemplo: eu estava escrevendo uma classe para validação de entrada de uma tela. A maioria dos campos eram detalhes comuns de um cliente – nome, sobrenome, etc. – além de campos para data de nascimento e idade. Eu comecei escrevendo um ClientDetailsValidatorTest
com métodos como testShouldFailForMissingSurname
e testShouldFailForMissingTitle
.
Comecei a desenvolver a lógica para calcular a idade e entrei num mundo confuso de regras de negócio: e se a idade e a data de nascimento forem preenchidas mas não coincidirem? E se a data de nascimento é hoje? Como calcular a idade se tudo que eu tenho é a data de nascimento? Eu estava escrevendo testes com nomes cada vez mais complicados para descrever tais comportamentos, então eu decidi move-los para outro lugar. Isso me levou a criar uma nova classe, AgeCalculator
, com seu próprio AgeCalculatorTest
. Com todos os comportamentos relacionados ao cálculo de idades movidos para a calculadora, o validador só precisava de um teste nessa área para garantir que ele interagia corretamente com a calculadora.
Se uma classe está fazendo mais de uma coisa, eu geralmente tomo como indicação que eu devo introduzir outras classes para fazer um pouco do trabalho. Eu defino o novo serviço como uma interface descrevendo o que ele faz, e passo o serviço através do construtor da classe:
public client ClientDetailsValidator { private final AgeCalculator ageCalc; public ClientDetailsValidator(AgeCalculator ageCalc) { this.ageCalc = ageCalc; } } |
Esta maneira de conectar os objetos, conhecida como “injeção de dependências” (dependency injection), é especialmente útil em conjunto com mocks.
Um nome de teste expressivo ajuda quando o teste falha
Depois de um tempo, descobri que quando eu modificava código que fazia um teste falhar, eu podia olhar para o nome do método de teste e identificar qual o comportamento esperado do código. Tipicamente, uma das três coisas havia acontecido:
- Eu introduzi um bug. Oops. Solução: consertar o bug.
- O comportamento esperado ainda era relevante mas se mudou para outro lugar. Solução: mover o teste e talvez modificá-lo.
- O comportamento não era mais esperado. Solução: remover o teste.
O último acontece comumente em projetos ágeis, conforme a compreensão da solução amadurece. Infelizmente, praticantes novatos de TDD têm um medo inato de apagar testes, como se de certa forma isso reduzisse a qualidade do código.
Um aspecto mais sutil da palavra “deveria” se torna aparente quando comparada com alternativas mais formais, como “irá” (“will”) ou “vai” (“shall”). “Deveria” lhe permite, implicitamente, duvidar da premissa do teste: “Deveria? Mesmo?” Isto torna mais fácil decidir se um teste está falhando por causa de um bug introduzido recentemente ou simplesmente porque suas premissas sobre o comportamento do sistema mudaram.
“Comportamento” é uma palavra mais útil que “teste”
Agora eu tinha uma ferramenta – agiledox – para remover a palavra “test” e um padrão para os nomes de métodos de teste. Me ocorreu, então, que as dificuldades que muitas pessoas tem com TDD quase sempre vem da palavra “teste”.
Isso não quer dizer que testar não é intrÃnseco ao TDD – o conjunto de métodos resultante é uma maneira efetiva de garantir que seu código funciona. No entanto, se os métodos não descrevem completamente o comportamento do sistema, eles provavelmente estão trazendo uma falsa sensação de segurança.
Comecei a usar a palavra “comportamento” no lugar de “teste” quando utilizava TDD e descobri não só que a palavra se encaixava no contexto, como também que uma categoria inteira de dúvidas se dissipou magicamente. Agora eu tinha respostas para alguma dessas questões sobre TDD. Fica fácil nomear seu teste: é uma sentença descrevendo o próximo comportamento em que você está interessado. Quanto testar se torna uma questão simples: não dá pra descrever em uma frase muito mais do que o comportamento necessário. Quando um teste falha, basta seguir o processo descrito anteriormente: ou um bug foi introduzido, ou o comportamento mudou ou o teste não é mais relevante.
Descobri que a diferença entre pensar em testes e pensar em comportamentos é tão profunda que comecei a me referir a TDD como BDD, ou Behaviour-Driven Development.
JBehave dá mais ênfase ao comportamento do que aos testes
No fim de 2003, decidi me dedicar a isso, era a hora de colocar dinheiro – ou pelo menos esforço – no que eu falava. Comecei a escrever um substituto ao JUnit chamado JBehave, que removia qualquer referência a testes, substituindo-as com um vocabulário construÃdo a partir da verificação de comportamento. Eu fiz isso para ver o quanto um framework assim evoluiria se eu aderisse estritamente aos meus mantras. Eu também pensei que seria uma ferramenta de ensino valiosa para introduzir TDD e BDD sem a distração causada pelo vocabulário baseado em testes.
Para definir o comportamento de uma classe hipotética chamada CustomerLookup
, eu escreveria uma classe de comportamento chamada, por exemplo, CustomerLookupBehaviour
. Ela conteria métodos que começam com “should” (“deveria”). O verificador de comportamentos iria instanciar essa classe e chamar cada método em sequência, assim como o JUnit. De forma similar, geraria um relatório e um resumo das operações executadas.
Minha primeira meta foi fazer com que o JBehave se auto-verificasse. Eu apenas adicionei o comportamento necessário para o JBehave se auto-executar. Eu pude então migrar todos os testes do JUnit para comportamentos do JBehave, obtendo o mesmo feedback imediato proporcionado pelo JUnit.
Determine o próximo comportamento mais importante
Então eu descobri o conceito de valor de negócio. Eu sempre tive consciência de que eu escrevia software por um motivo, é claro, mas nunca tinha realmente pensado sobre o valor do código que eu estava escrevendo a cada momento. Outro colega, o analista de negócios Chris Matts, me fez pensar sobre o valor de negócio no contexto de BDD.
Já que eu tinha em mente o objetivo de tornar o JBehave auto-contido, eu achei uma maneira bem útil de me manter focado nas perguntas que eu tinha que fazer: qual é a próxima coisa mais importante que o sistema não faz?
Esta pergunta faz com que você identifique o valor das funcionalidades que ainda não estão implementadas e priorize-as. Ela também ajuda a formular o nome do método de comportamento: o sistema não faz X (onde X é algum comportamento significativo), e X é importante, o que quer dizer que ele deveria fazer X; então seu próximo método de comportamento é simplesmente:
public void shouldDoX() { // … } |
Agora eu tinha a resposta para outra dúvida a respeito de TDD: saber por onde começar.
Requisitos também são comportamentos
Nesse ponto, eu tinha um framework que me ajudava a entender – e mais importante, explicar – como TDD funciona e uma abordagem que evitava todas as armadilhas que eu já havia encontrado.
Ao fim de 2004, enquanto eu estava descrevendo o meu novo vocabulário para o Chris, ele disse: “Mas isso é exatamente como análise.” Houve uma longa pausa enquanto processávamos isso, e então decidimos aplicar todo esse pensamento orientado a comportamentos para definir requisitos. Se pudéssemos desenvolver um vocabulário consistente entre analistas, testadores, desenvolvedores e o cliente, estarÃamos no caminho certo para eliminar algumas das ambiguidades e problemas de comunicação que ocorrem quando técnicos falam com clientes.
BDD oferece uma “linguagem ubÃqua” para análise
Nessa época, Eric Evans publicou seu livro best-seller Domain-Driven Design. Nele, ele descreve o conceito de modelar um sistema usando uma linguagem ubÃqua baseada no domÃnio de negócio para que o vocabulário de negócio também permeie o código.
Chris e eu percebemos que estávamos tentando definir uma linguagem ubÃqua para o próprio processo de análise! Nós tÃnhamos um bom inÃcio. Um padrão para escrever histórias já estava sendo usado na empresa, seguindo o seguinte modelo:
Como um [X]
Eu gostaria de [Y]
Para que [Z]
Onde Y é alguma funcionalidade, Z é o benefÃcio ou valor de negócio da funcionalidade, e X é a pessoa (ou papel) que vai se beneficiar dela. A vantagem é que ela te força a identificar o valor de entregar uma história quando você inicialmente a define. Quando não existe valor de negócio real para uma história, ela normalmente acaba definida como algo do tipo “…Eu gostaria de [alguma funcionalidade]… para que [porque eu quero, ok?]” Fazer isto também pode tornar mais fácil remover alguns requisitos mais esotéricos do caminho.
A partir deste ponto, o Chris e eu começamos a descobrir o que todo testador ágil já sabe: o comportamento de uma história é simplesmente seu teste de aceitação – se o sistema atende a todos os critérios de aceitação, ele está se comportando corretamente; caso contrário, não. Então criamos um modelo para capturar os critérios de aceitação de uma história.
O modelo deveria ser flexÃvel o suficiente para não parecer artificial ou restritivo para os analistas, porém estruturado o suficiente para que pudéssemos quebrar as histórias em pedaços que pudessem ser automatizados. Nós começamos a descrever os critérios de aceitação com base nos cenários, da seguinte forma:
Dado algum contexto inicial (entradas),
Quando um evento ocorre (eventos),
Então verifique alguns resultados (saÃdas)
Para ilustrar, vamos usar o exemplo clássico de um caixa automático. Uma das histórias poderia ser mais ou menos assim:
TÃtulo: Cliente retira dinheiro
Como um cliente,
Eu gostaria de retirar dinheiro de um caixa automático,
Para que eu não tenha que esperar numa fila de banco.
Como sabemos quando essa história está entregue? Existem diversos cenários a considerar: a conta pode estar em crédito, a conta pode estar negativa mas com limite ou ainda estar com o limite estourado. Haverão outros cenários, é claro, como quando a conta está em crédito mas uma retirada a torna negativa ou quando o caixa automático não tem dinheiro suficiente para completar a operação.
Usando o modelo entradas-eventos-saÃdas (given-when-then), os dois primeiros cenários poderiam ficar assim:
Cenário 1: Conta está em crédito
Dado que a conta está em crédito
E o cartão é válido
E o caixa tem dinheiro suficiente
Quando o cliente requisita dinheiro
Então verifique que a conta foi debitada
E verifique que o dinheiro é entregue
E verifique que o cartão é devolvido
Perceba o uso da palavra “e” para conectar múltiplas entradas e múltiplas saÃdas de forma natural.
Cenário 2: Conta está negativa além do limite
Dado que a conta está negativa
E o cartão é válido
Quando o cliente requisita dinheiro
Então verifique que uma mensagem de rejeição é mostrada
E o dinheiro não é entregue
E o cartão é devolvido
Ambos cenários são baseados no mesmo evento e até tem algumas entradas e saÃdas em comum. Idealmente, nós queremos economizar tempo e reusar entradas, eventos e saÃdas.
Critérios de aceitação devem ser executáveis
Os fragmentos do cenário – as entradas, eventos e saÃdas – são suficientemente detalhados para ser representados diretamente no código. O JBehave define um modelo de objetos que permite mapear um cenário diretamente em classes Java.
Você escreve uma classe representando cada entrada (given):
public class AccountIsInCredit implements Given { public void setup(World world) { … } } public class CardIsValid implements Given { public void setup(World world) { … } } |
e uma para o evento (when):
public class CustomerRequestsCash implements Event { public void occurIn(World world) { … } } |
e assim por diante, para as saÃdas (then). O JBehave então conecta todos estes objetos e os executa. Ele cria um “mundo”, que é apenas um lugar para guardar seus objetos, e o passa para cada uma das entradas, uma de cada vez, para que elas populem o mundo com informações conhecidas sobre o estado da aplicação. O JBehave então diz ao evento para “ocorrer dentro do” mundo, que efetivamente roda o comportamento do cenário. Finalmente, ele passa o controle à s saÃdas que definimos para a história.
Usando uma classe para representar cada fragmento nos possibilita a reutilização em outros cenários ou histórias. Inicialmente, os fragmentos são implementados usando mocks para criar uma conta que está em crédito ou um cartão válido. Estes servem como ponto de partida para implementar os comportamentos. Conforme você implementa a aplicação, as entradas e saÃdas são alterados para usar as classes de verdade que foram implementadas,de forma que quando a implementação do cenário for finalizada, você terá verdadeiros testes funcionais de ponta a ponta.
O presente e o futuro do BDD
Após uma breve pausa, o JBehave está novamente em desenvolvimento. O núcleo está bem completo e robusto. O próximo passo é a integração com IDEs Java populares, como o IntelliJ IDEA e o Eclipse.
Dave Astels está promovendo o BDD ativamente. Os posts no seu blog e diversos artigos publicados provocaram uma enxurrada de atividades, mais notavelmente o projeto RSpec, um framework para BDD escrito em Ruby. Eu comecei a trabalhar no RBehave, que será uma implementação do JBehave em Ruby (nota do tradutor: atualmente, o projeto ativo é o cucumber, inspirado no Story Runner implementado por Dan North no RBehave).
Vários colegas de trabalho têm usado técnicas de BDD em diversos projetos do mundo real e as técnicas se mostraram eficientes. O mecanismo de execução de histórias do JBehave – a parte que verifica os cenários de aceitação – está em constante desenvolvimento.
A visão é ter um editor integrado que permite que analistas de negócio e testadores capturem histórias num editor de textos que pode gerar stubs para as classes de comportamento, todos numa linguagem compatÃvel com o domÃnio de negócio. O BDD evoluiu com a ajuda de muitas pessoas e sou profundamente grato a todas elas.
September 8th, 2009 at 7:49 pm
Excelente tradução. Só um ponto, entre as ferramentas citadas não podemos deixar de mencionar o Fitnesse. Também um excelente framework BDD. Abraços!
June 13th, 2010 at 8:39 pm
[…] Introduzindo Desenvolvimento Orientado por Comportamento (BDD) […]
July 19th, 2010 at 4:52 pm
[…] – Como? Talvez esta seja uma das informações mais importantes. O desenvolvedor precisa saber como será utilizado o recurso que está sendo trabalhado. Ou seja, ele precisa saber quais são os critérios de aceitação deste recurso. Este link pode ajudar. […]
July 16th, 2011 at 7:06 pm
[…] Danilo Sato – Introduzindo Desenvolvimento Orientado por Comportamento. Esta entrada foi publicada em Desenvolvimento e marcada com a tag agile, bdd, testes. Adicione o link permanente aos seus favoritos. ← TDD: Desenvolvimento Orientado a Testes Aniversário: 3 meses de blog! → […]
June 12th, 2012 at 5:41 pm
Parabéns, ótima referência para o assunto.