Testes de código: definções e principais aspectos
Tags: typescript, javascript, beginners, tdd
Published: 2023-01-28 20:00:00

Ainda que a abordagem mais conhecida seja a da pirâmide de testes, não necessariamente ela é a mais realista, isso porque na "vida real" do desenvolvimento de software testes se encaixariam muito melhor num espectro do que realmente dentro de uma pirâmide. Uma "pirâmide" reflete fronteiras claras entre o que é exatamente um teste de integração e um teste de unidade, mas essas fronteiras praticamente não existem na vida real. Justamente por isso essa definição do que é um "teste de unidade" e um "teste de integração" quase sempre é uma definição _ad hoc_ feita para cumprir as expectativas de um determinado framework.
No [NestJs](https://docs.nestjs.com/fundamentals/testing#end-to-end-testing), por exemplo, um teste de integração é basicamente um teste de fluxo de um endpoint o que também poderiamos definir como um test end to end. As definições do que é um determinado tipo de testes são generalistas e malmente cobrem a realidade.

![Pirâmide de testes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40qv8sn7elh1cp6m6o2w.png)

Na grande maioria dos projetos, nossos testes estarão muito mais próximos do conceito de testes de integração ainda que vejamos eles como unitários. Não é trivial definir uma unidade e muitas vezes elas sequer existem. Variáveis como acoplamento influenciam na forma como os testes são implementados. No geral compreendemos testes unitários como o testes de uma determinada função ou método, ele são mais fáceis de fazer e executam mais rápido.

## Testes de integração

Determinam se partes desenvolvidas independentemente funcionam corretamente quando estão conectadas. Muitas pessoas pensam que testes de integração possuem um escopo grande, mas eles podem ser feitos de maneira muito mais efetiva se possuírem um escopo mais reduzido. Antigamente era comum um programador ser responsável por toda a construção de um determinado módulo e essa construção poderia levar meses, todo esse trabalho era feito em isolamento e quando o programador acreditava que o módulo estava pronto ele entregava para o Quality Assurance para que pudesse ser testado.

O ponto sobre testes de integração é testar se módulos que foram desenvolvidos separadamente trabalham juntos como o esperado. Um outro ponto de ver testes de integração é testar se um sistema formado por um conjunto de módulos trabalham como esperado. Mas essas duas coisas podem se fundir facilmente.

### Tipos de teste de integração

Temos dois tipos de testes de integração:
- **narrow**: responsável por testar apenas a parte do meu service que se comunica com outra parte externa consistindo em muitos testes de escopo restrito de forma com que se assemelham muito com testes unintários e são marcados pelo uso de _doubles_

- **broad**: Requerem versões válidas de todos os serviços, requisitando muitos testes de ambiente e acesso a rede, testam um ciclo de vida completo de uma requisição, não apenas código responsável por interações

![Tipos de testes de integração](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/44rwlakyzrzmxgtdf9w4.png)

Para a grande maioria da população dos desenvolvedores testes de integração são necessariamente do tipo broad, o que os deixa um pouco confusos quando se deparam com outra abordagem. Muitos ainda chamam testes broad de testes end to end.

## Testes unitários

A pesar de ser muito citado no contexto de desenvolvimento de software o que define um teste unitário ou teste de unidade nunca fica muito claro, o que muitas vezes causa certo tipo de confusão. É muito difícil definir exatamente o que é um teste unitário, porém podemos entender que há alguns padrões que eles seguem.

Testes unitários são:

- De baixo nível
- Foco numa pequena parte do sistema
- Mais rápidos que outros tipos de testes

Porém a pesar de haver essas semelhanças temos diferenças no que as pessoas esperam ser uma “unidade”, designs orientados a objetos tendem a criar uma visão de que uma unidade seria uma classe, enquanto que abordagens procedurais e funcionais fazem parecer que uma função é uma unidade, mas isso não é tão preto no branco quanto parece. O time deve decidir o que é uma unidade com o propósito do entendimento do sistema e de como ele é testado. 

A abordagem mais comum que temos é realmente que uma unidade seja uma classe, porém com certa frequência, um conjunto de classes relacionadas também podem ser vistas como uma unidade.

### Tipos de testes unitários

Testes unitários podem ser sociáveis ou solitários, imagine uma classe `Order` que possui o método `calculateTotalPrice()` e esse método por sua vez precisa invocar métodos nas classes `Customer` e `Product`. Para termos testes unitários solitários eu não posso usar a implementação real dessas classes nos meus testes, assim sendo tenho de criar test doubles para que possa fazer o teste solitário do método `calculateTotalPrice()`.

> Um teste unitário solitário faz uso de teste doubles, como mocks, stubs, spies e etc…

Mas nem sempre encontramos testes unitários solitários, muitas vezes encontramos testes unitários sociáveis, isto é, um teste unitário que interage com outras classes, que do ponto de vista de alguns seria uma “unidade”.

![Tipos de testes unitários](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysbr574afh9ct9xfc4bz.png)

## F.I.R.S.T

É um acrônimo para definir qual a realidade dos testes dentro da aplicação, ele responde a pergunta "o que um teste deve ser?"

- **F**ast: testes devem rodar rápido, para encorajar que eles possam rodar mais de uma vez.
- **I**ndependent: os testes não devem depender uns dos outros para que possam executar
- **R**epeatable: o resultado deve ser o mesmo independente da quantidade ou da qualidade da execução dos testes
- **S**elf-validating: o próprio teste deve possuir uma saída bem definida que é válida ou não, fazendo com que ele passe ou falhe.
- **T**hrough: um teste deve “atravessar” o caso que testa, testando cenários felizes e de falha, segurança e outros problemas.

### Podemos ter mais de uma verificação por teste?

Muito se prega que "devemos ter apenas uma asserção por teste", teoria defendida principalmente no livro Clean Code, mas essa definição não tem muita validade em testes na vida real.

Podemos sim ter mais de uma verificação por testes desde que as asserções verifiquem um mesmo cenário, por exemplo ao cadastrar um novo cliente posso buscar pelo número de telefone e email para verificar se a criação aconteceu devidamente.

## Tips and tricks

- Lista de cenários: Tenha uma lista de cenários iniciais antes de conceber os seus testes, saiba ao menos 2 ou 3 casos de saída com bases em entradas. Isso vai ajudar bastante a desenvolver a aplicação.

- Passos pequenos: Ter passos pequenos é uma das principais características do TDD, devemos fazer as coisas aos poucos até chegar ao resultado desejado, não seja ansioso.

- Triangulação: Mesmo já possuindo um código de produção que funciona devemos adicionar testes com corpos parecidos, porém que farão o teste de produção falhar, essa técnica é o que chamamos de triângulação.

- Código limpo: Lembre-se que códigos são a melhor forma de documentação do seu sistema, assim sendo é muito importante que eles estejam escritos tão bem quanto o seu código de produção (e se possível até melhores).