TDD

TDD by Kent Beck

Acabei de assistir o vídeo de TDD que o Kent Beck gravou para os Pragmatic Programmers. Pra quem não conhece, o cara é a mente brilhante por trás de XP, TDD e Design Patterns.

No vídeo, Kent implementa um wrapper em Java para o Tokyo Tyrant, um servidor de banco de dados bem leve que serve como uma hashtable remota de bytes. O exemplo é inusitado.

Não há nada muito novo pra quem pratica TDD e/ou já leu o livro do mesmo Kent Beck sobre o assunto, o TDD by Example.

Uma idéia interessante é fazer Scaffolding Tests, algo como “Testes Andaime” em português. São testes que servem de suporte para você implementar uma funcionalidade e que, a princípio, expõe uma implementação interna da classe que você está desenvolvendo.  À medida que você avança na implementação, pode esconder essas implementações internas em métodos privados e remover os testes correspondentes. Os testes da API pública da classe já exercitarão esses métodos privados. Quem tem um pouco de experiência com TDD, acaba fazendo frequentemente.

Outra ideia legal é o que ele chama de TDD Games: brincar com a ordem das suas decisões de design, apagando código e seguindo novos rumos, pra aprender o efeito das decisões no rumo do design.

Kent chama a atenção para o fato de todo teste implementado é um custo a mais no projeto e cada teste não implementado é um potencial risco. Ao praticar TDD, é importante balancear entre esses dois fatores: custo x risco.

Para Kent Beck, cada teste deve ser uma pequena estória, com início, meio e fim. Tanto no nível da história, como de uma funcionalidade ou de um sistema, é mantido um ritmo semelhante:  definição do problema, desenvolvimento e limpeza do código. Esse ciclo se repete em vários níveis, desde minutos para implementar um história, como dias para implementar uma funcionalidade e semanas para implementar um sistema (ou sub-sistema). A limpeza do código serve como um fechamento do ciclo. É fácil saber quando você acabou uma etapa e o código se mantém limpo.

Assim, é atingido o objetivo: código limpo e que funciona.

Anúncios
OO, TDD

TDD favorece a estabilidade

Mauricio Aniche escreveu um ótimo post falando sobre TDD e estabilidade.

Estabilidade é uma medida da improbabilidade de uma classe ser mudada. Se uma dada classe depende diretamente de outras classes é mais provável que ela seja mudada, do que se ela depender só de interfaces.

Mauricio conclui:

TDD faz com que o programador acople suas classes com módulos geralmente mais estáveis!

Por quê?

Porque TDD obriga você a pensar apenas no que você espera das outras classes, sem pensar ainda em uma implementação concreta.

Esses comportamentos esperados acabam geralmente se transformando em interfaces, que frequentemente se tornam estáveis.

Muito bom!

Leia mais: TDD diminui o acoplamento, mas só isso não resolve!

TDD

Desenvolvendo uma calculadora pós-fixada com TDD

Bret Schuchert, da Object Mentor (a empresa de Uncle Bob),  criou uma série de screencasts em que ele ensina conceitos de TDD, refatoração e design emergente enquanto desenvolve uma calculadora pós-fixada (do estilo da HP) em Java.

A série é muito boa. Bret é muito bem-humorado e tem bons insights de design durante os vídeos.

Abaixo está o primeiro vídeo e alguns pontos que achei interessantes.

Uma das partes que achei mais interessantes é quando ele renomeia a classe RpnCalculator deixando os nomes muito mais claros:

public class RpnCalculator {
  @Test
  public void NewCalculatorHas0InItsAccumulator { ... }
  @Test
  public void NewAccumulatorShouldAllowItsAccumulatorToBeSet { ... }
}

vira

public class ANewlyCreatedRpnCalculatorShould {
  @Test
  public void Have0InItsAccumulator { ... }
  @Test
  public void AllowItsAccumulatorToBeSet { ... }
}

Bret diz que a inspiração para a mudança de nome vem do BDD, metodologia que se preocupa muito com a maneira como as coisas são representadas no código. Realmente ajuda bastante.

Na mesma linha de raciocínio, há uma mudança muito interessante que Bret faz em um código que caminha na direção de um assert por teste e remove duplicação do código.

public class ANewlyCreatedRpnCalculatorShould {
  private RpnCalculator calculator;
  @Before
  public void init(){
    calculator = new RpnCalculator();
  }
  @Test
  public void Have0InItsAccumulator{ ... }
  @Test
  public void AllowItsAccumulatorToBeSet{ ... }
  @Test
  public void AllowMultipleValuesToBeStored(){
    BigDecimal value = new BigDecimal(42);
    BigDecimal value2 = new BigDecimal(2);
    BigDecimal value3 = new BigDecimal(3);
    calculator.setAccumulator(value);
    calculator.enter();
    calculator.setAccumulator(value2);
    calculator.enter();
    calculator.setAccumulator(value3);
    assertEquals(value3, calculator.getAccumulator());
    calculator.drop();
    assertEquals(value2, calculator.getAccumulator());
    calculator.drop();
    assertEquals(value, calculator.getAccumulator());
  }
}

vira

public class ACalculatorWithThreeValuesShould{

  private RpnCalculator calculator;
  BigDecimal value = new BigDecimal(42);
  BigDecimal value2 = new BigDecimal(2);
  BigDecimal value3 = new BigDecimal(3);

  @Before
  public void init(){
    calculator = new RpnCalculator();
    calculator.setAccumulator(value);
    calculator.enter();
    calculator.setAccumulator(value2);
    calculator.enter();
    calculator.setAccumulator(value3);
  }

  @Test
  public void HaveTheLastValueEnteredInItsAccumulator(){
    assertEquals(value3, calculator.getAccumulator());
  }

  @Test
  public void HaveTheSecondToLastValueAfterASingleDrop{
    calculator.drop();
    assertEquals(value2, calculator.getAccumulator());
  }

  @Test
  public void HaveTheFirstValueEnteredAfterTwoDrops(){
    calculator.drop();
    calculator.drop();
    assertEquals(value, calculator.getAccumulator());
  }

  @Test
  public void Have0AfterThreeDrops(){
    calculator.drop();
    calculator.drop();
    calculator.drop();
    assertEquals(BigDecimal.ZERO, calculator.getAccumulator());
  }
}

Ao mover um código que testava três coisas ao mesmo tempo para uma classe específica, Bret pôde fazer um único método que criou as fixtures para os demais testes.  Assim, foi possível separar cada assert em um método com uma responsabilidade bem específica e melhorou bastante o design e a clareza do código.

Um outro momento interessante é quando Bret identifica inveja de funcionalidades na classe RpnCalculator em relação à classe Stack do Java. Esse mal-cheiro de código o faz discutir se a classe RpnCalculator está com responsabilidades demais. Então, Bret extrai a classe OperandStack (com os devidos testes antes), que fica responsável por utilizar a classe Stack.

Em um vídeo posterior, Bret adiciona as funções de adição, subtração e fatorial, sempre mudando a classe RpnCalculator para implementar essas operações. Ele diz que isso viola o princípio Open/Closed, que diz que entidades de software tem que ser abertas para extensão e fechadas para modificação. Para implementar uma operação de divisão na RpnCalculator, por exemplo, teríamos que modificar o código da classe. Então, Bret usa o pattern Strategy, e define uma interface chamada MathOperator para as operações e classes Add, Subtract e Factorial que implementam essa interface. É interessante notar que, ao invés de definir operandos diferentes para cada uma das operações, a interface recebe a pilha de números armazenados. Isso evita problemas na definição de uma mesma interface para operadores unários (fatorial) e binários (adição e subtração).

É interessante ver o ciclo red/green/refactor em ação. A parte mais rica, onde o aprendizado de software design mais acontece, é na refatoração. Mas sem o red/green, a refotaração não seria uma atitude responsável a ser tomada.

TDD

Test Driven Development By Example

Li recentemente o livro TDD By Example. É um livro excelente, uma introdução à mentalidade do TDD. Talvez falte algo mais parecido com o dia-a-dia dos projetos reais, mas não é esse o foco do livro.

O nome do livro é “By Example” porque há dois exemplos no livro, que tem o mesmo “jeitão” de katas:

  • o primeiro problema é a soma de moedas diferentes, no caso francos suíços e dólares. O primeiro exemplo é implementado em Java. Kent Beck vai mostrando como ele resolveria o problema desde o começo e toma rumos surpreendentes por duas vezes :
    • Kent começa com uma classe Dólar. Quando há a necessidade de mais uma moeda, ele cria a classe Moeda, torna Dólar subclasse de Moeda e cria a subclasses Franco. Mais pra frente, ele remove as duas subclasses (Dólar e Franco) e as diferencia apenas como atributos da classe Moeda. É muito estranho remover classes, mas depois descobri que “minimizar classes e métodos” é uma das regras do design emergente do XP. O mais incrível é que ter apenas uma classes Moeda realmente facilita o trabalho mais pra frente.
    • Para fazer a conversão em si, Kent evita um código procedural cheio de ifs.  A responsabilidade de fazer a conversão dos valores em uma mesma moeda é de uma classe chamada Banco. Kent discute uma abordagem que usa como metáfora uma carteira. Mas na implementação do livro, ele usa a idéia de expressões, que é muito mais flexível.
  • o segundo problema é a criação de um framework de test unitário usando TDD. Muito doido, não? Esse exemplo é dado em Python.

Segui o código da primeira parte do livro, “The Money Example” e coloquei o código no meu github: http://github.com/alexandreaquiles/the_money_example

Na última parte do livro o cara discute sobre a filosofia de TDD, sobre design patterns, refactoring, etc… Essa talvez seja a parte mais rica do livro.

Kent sugere é que você comece o dia com uma lista do que o seu código deve fazer. Você deve ir criando testes que exercitem os requisitos dessa lista e, à medida que você descobre novas responsabilidades para o seu código, você insere novos itens na lista. A lista pode ser de papel mesmo. O importante é manter um registro dos pensamentos que surgem enquanto você codifica, para que eles não “fujam”. Manter essa lista é ajuda a pensar no seu design.

Outra coisa muito importante no TDD é fazer as coisas em baby steps, ou passos de bebê. A idéia é fazer as coisas aos poucos, um pouco de testes e um pouco de código de cada vez. Isso faz com que você se perca menos e foque mais naquela parte do problema que você está resolvendo. Além disso, se um teste que estava passando passa a falhar, você sabe exatamente o que você estava fazendo, evitando um bom tempo de debug.

Uma das frases mais interessantes dessa terceira parte do livro diz respeito a, quando você tem um teste falhando, como você deve implementar:

Se você sabe o que escrever, use a Implementação Óbvia. Se você não sabe o que escrever, use uma Implementação Simulada (fake it). Se o design correto não está claro, Triangule. Se você ainda não souber o que escrever, pare tudo e vá dar uma relaxada.