Quando usar TDD
Tags: testing, productivity
Published: 2020-07-25 22:09:20

_Test-Driven Development_, ou Desenvolvimento Orientado a Testes em português, conhecido por sua sigla TDD é uma prática de engenharia de software que nunca foi unanimidade e que, passado o _hype_, tem sido mais criticada do que apoiada nos círculos mais populares.

Pelo título, dá pra perceber que eu acredito que a prática tem seu lugar e tentarei explicar meu ponto de vista nesse texto, passando por definição, pontos positivos e negativos. Esse artigo reflete minha opinião, então não é completamente imparcial, mas tentarei passar por todos os pontos que creio relevantes.

## O que é TDD?

Talvez a definição não seja muito óbvia caso não seja familiar com o tema, mas Desenvolvimento Orientado a Testes quer dizer que o processo de escrita de código começa pela escrita dos testes, e somente após os testes estarem escritos (e falhando) que a implementação do código começa. Esse ciclo é chamado "Red-Green-Refactor".

![Processo "red-green-development"](https://dev-to-uploads.s3.amazonaws.com/i/akbh5smbv8oeq8c9lho5.png)

A ideia é que dessa maneira, antes de começar a escrever o código, as pessoas desenvolvedoras procurem descobrir quais são as _regras de negócio_ que o código deve satisfazer, e quais são as _expectativas_ daquele código.

## Resolvendo FizzBuzz, um exemplo de TDD em prática

Para ilustrar a ideia, segue um exemplo de solução do problema _FizzBuzz_. A definição do problema é, para todo inteiro `n` entre 1 e 100:

* Se `n` for divisível por 3, retorne `Fizz`.
* Se `n` for divisível por 5, retorne `Buzz`.
* Se `n` for divisível por 3 e 5 ao mesmo tempo, retorne `FizzBuzz`.

Usarei Java nos exemplos mas pessoas familiarizadas com código devem entender a ideia ainda que não todos os detalhes.

Estrutura inicial:

```java
public class FizzBuzz {

    public String fizzBuzz(final int numero) {
        return "";
    }
}
```

O primeiro passo é decidir a lógica de negócio e escrever nossos testes. Vamos seguir o ciclo _Red-Green-Refactor_, então o primeiro teste que eu vou escrever é cobrindo a primeira regra, _Se `n` for divisível por 3, retorne `Fizz`_.

```java
public class FizzBuzzTest {

    @Test
    public void fizzBuzz_RetornaFizz_QuandoMultiploDe3() {
        final String esperado = "Fizz";
        
        final FizzBuzz fizzBuzz = new FizzBuzz();
        final String resposta = fizzBuzz.fizzBuzz(3);
        
        assertThat(resposta, CoreMatchers.equalTo(esperado));
    }
}
```
Ao rodar o teste, encontramos o esperado, o teste falha:

```
java.lang.AssertionError: 
Expected: "Fizz"
     but: was ""
Expected :Fizz
Actual   :
```
Como em nosso código sempre retornamos uma String vazia, precisamos implementar o código necessário para que esse teste passe. Atualizando a função `fizzBuzz` mostrada anteriormente:

```java
public String fizzBuzz(final int numero) {
    if (numero % 3 == 0) {
        return "Fizz";
    }

    return "";
}
```
E agora, ao rodar o teste novamente, vemos que ele passa. O próximo passo seria fazer um teste para quando `n` for divisível por 5 e depois por 3 e 5. Sempre começando por um teste falhando e fazendo ele passar. 

[Cheque aqui][1] o repositório desse projeto para ver como o código e seus testes ficam.

## Pontos positivos

Depois de seguir o processo, podemos tecer opiniões sobre o ciclo. Primeiro vou escrever o que penso ser bom no processo.

> Pró #1: Primeiro pensar na regra de negócio antes de escrever código

Ao primeiro pensar nos testes, a pessoa desenvolvendo é obrigada a pensar nas regras de negócio, em quais são os casos complicados e se compreenderam o problema. Isso é positivo pois a função do código é satisfazer as regras de negócio, então começar por elas faz sentido.

> Pró #2: TDD incentiva a escrita de código testável

Por primeiro escrever os testes, o código por consequência fica organizado de uma maneira que é mais fácil de testar. Usando o mesmo problema FizzBuzz como exemplo, seria possível escrever apenas uma função:

```java
public void resolveTudoEmUm() {
    for (int i = 1; i <= 100; i++) {
        String resultado = "";
        if (i % 3 == 0) {
            resultado += "Fizz";
        }
        if (i % 5 == 0) {
            resultado += "Buzz";
        }
        System.out.println(resultado);
    }
}
```
Essa função executa corretamente, mas é difícil de testar unitariamente. Lógico que nesse exemplo de problema simples é fácil de verificar manualmente, mas assim que os problemas se tornam mais complexos, os testes manuais ficam menos confiáveis.

Código sem teste é código legado, indepedente da "idade", já falei sobre isso em [outro post][2].

TDD pode ser extremamente útil especialmente quando lidando com problemas complexos e para acostumar a escrever código mais testável.

## Pontos negativos

Muito já foi escrito sobre porque não utilizar TDD e eu vou tentar cobrir os que são mais importantes na minha opinião.

> Contra #1: O desenvolvimento fica mais lento

Para qualquer um que tente seguir o ciclo TDD, uma das primeiras impressões é que a técnica parece diminuir muito a velocidade de desenvolvimento, e seguir à risca pode significar muitos ciclos entre escrever um teste de cada vez e o código associado.

Eu concordo com essa crítica, mas sugiro uma mitigação: para casos óbvios de vários testes, sinta-se à vontade para escrever todos os testes de uma vez e implementar aos poucos. É muito importante manter um ciclo curto entre os testes para garantir o progresso e que nenhum caso deixe de funcionar, mas isso diminui o número de interrupções no processo.

> Contra #2: TDD não é a única maneira de escrever código testável

É possível ter os pontos positivos do TDD - ter código testável e pensar na lógica primeiro - sem ter que seguir o processo do TDD que por vezes é muito _baby steps_ e cheio de interrupções.

Esse é o principal motivo pelo qual eu não uso TDD sempre, mas ainda acho o TDD uma técnica muito útil para aumentar o conhecimento sobre como escrever código testável.

## Conclusão

Usar TDD influencia positivamente o código gerado, de modo geral o resultado é mais simples de testar e garantir que as regras de negócio são cumpridas. É também muito útil para aprender como escrever código testável.

Porém é uma técnica que, caso seguida à risca, promove muitas interrupções no fluxo de desenvolvimento, o que pode ser frustrante e diminuir muito a velocidade de desenvolvimento.

Particularmente, eu acho excelente para novos serviços em que as regras de negócio não estão super definidas e também para parear com pessoas com menos experiência em escrever testes e código testável.

[1]: https://github.com/gpiress/FizzBuzzTDD
[2]: https://medium.com/@gpiress/trabalhando-com-c%C3%B3digo-legado-sem-enlouquecer-8155b5ec5e8a