TDD e seus fundamentos
Saiba o que são testes, seus valores, benefícios e como utilizá-los.
Table of contents
Olá! Imagino que você tenha ouvido falar bastante sobre TDD e esteja interessado em aprender e praticar os famosos testes de desenvolvimento. Esse texto tem o objetivo de te ajudar nisso!
Primeiramente começamos com,
O que é TDD?
TDD é um acrônimo para Test-Driven Development, ou seja, Desenvolvimento Guiado por Testes.
No contexto de programação temos os desenvolvedores, encarregados de solucionar problemas com soluções que funcionam, e os testers, encarregados de realizarem testes próximos aos do consumidor final.
Porém, apesar dos testers estarem encarregados de realizar testes sobre o código dos desenvolvedores, os desenvolvedores ainda devem realizar testes próprios. Isso porque é improdutivo enviar e receber correções para testers, pois isso toma tempo e esforço de muitas pessoas, atrasando o desenvolvimento.
Pensando nisso, os desenvolvedores então passaram a criar testes para suas aplicações de forma a assegurar que seus códigos funcionam minimamente de acordo com o planejado, sem bugs. Eles seguiam a ordem de desenvolver a funcionalidade e então aplicar testes em cima.
O problema acontece no ponto em que testar algo que você construiu atrapalha na perspectiva de encontrar problemas, pois você vai estar enviesado pelo seu desenvolvimento. Nesse sentido, os desenvolvedores começaram a realizar os testes antes do desenvolvimento da funcionalidade
É aí que entra o TDD.
O que TDD não é:
TDD não é
- Método de testes / Técnica de QA
- Substituto de QA/Testers
O que TDD é:
TDD é
- Uma "técnica de design" para "designs emergentes"
- Testar antes de desenvolver
- Exemplos sobre o que o "Sistema deve fazer"
- Especificações vivas
Origem do TDD
Sua origem data de 1990 quando Kent Beck desenvolveu essa técnica de desenvolvimento de softwares onde o teste era feito antes do desenvolvimento como parte do eXtreme Programming (XP).
eXtreme Programming é um framework ágil de desenvolvimento de software.
Pessoas importantes que apoiam o TDD
Ciclo de desenvolvimento de TDD
Iniciamos com uma lista de requerimentos, geralmente as funcionalidades requisitadas. Depois, escrevemos os testes para cada item da lista, partindo do mais básico. Por exemplo, se determinado método existe. Você irá então rodar o teste e fazer ele falhar.
Após, você irá escrever o código mais simples para fazer o teste passar. Escreveremos apenas o necessário para fazer o teste passar, nada mais, nada menos.
Então rodamos o teste novamente e fazemos ele passar!
Por fim, refatoramos o código para verificar o que podemos melhorar no código sem modificar a funcionalidade, e então repetimos o ciclo novamente de pensar em um novo teste para a funcionalidade.
E aqui temos o famoso "Red, Green, Blue"!
Red - Faça ele falhar
- Código é testável
- Escrevemos e especificamos a validação
Green - Faça ele passar
- Código de acordo com o requerimento
- Solução mais simples que funciona
- Feito para passar no teste
Blue - Refatore
- Limpeza do código
- Identação
- Remove code smells
- Repensa o design
- Deleta código desnecessário.
- Boas práticas!
Benefícios do TDD
- Redução de bugs: reduz o tempo de testes de QA
- Melhor design de código: Código melhor reutilizado, Não redundante, Coeso, Sem acoplamento e limpo!
- Trabalho em equipe: O time passa a entender melhor o código pela implementação dos testes e a se comunicar melhor para implementar os testes corretos.
- Documentação: Os testes passam a ser uma documentação viva do código.
- Eficiência: Os próximos códigos serão mais eficientes, visto que os testes fundamentais para que o projeto funcione bem estão presentes. Não haverá necessidade de perder tempo pensando se outra parte do projeto irá quebrar.
- Manutenção: Como já existem testes implementados, qualquer mudança não passará despercebida. O número de bugs é bem menor.
Dificuldades com TDD
Algumas das dificuldades envolvem:
- Uso de Testes de Integração
- Testar certos casos como: UI, DB, Network, Sistemas externos, Arquivos, Código legado
- Investimento de tempo
- Falso senso de segurança. Como TDD não substitui QA, nós temos uma falsa sensação de segurança.
Testes antes do desenvolvimento VS Testes depois do desenvolvimento
Podemos ver no gráfico do livro "Growing Object-Oriented Software, Guided by Tests" uma explicação simples sobre a utilização de testes antes vs testes após o desenvolvimento.
Quanto desenvolvemos primeiro, temos que o início é tranquilo, estamos entregando e fazendo novas funcionalidades com tranquilidade. Porém quando nos aproximamos da deadline, temos que o caos/estresse aumenta exponencialmente. Isto porque ao adicionar novas funcionalidades o projeto passa a ficar instável, alguns bugs passam despercebidos, ou até retornam do QA, o que toma mais tempo para serem solucionados. As funcionalidades novas passam a quebrar as antigas, e corrigir as novas com gambiarras passa a ser mais fácil do que corrigir ambas. Temos uma bomba relógio.
Quando utilizamos testes antes, temos que inicialmente o desenvolvimento é estressante, visto que demora para progredir, porém ao avançarmos no tempo, as novas funcionalidades se tornam tranquilas para implementar. Não existem tantos bugs, e poucas coisas retornam do QA. Como novas funcionalidades não podem quebrar as antigas, o código avança com fluidez. O código se mantém claro e documentado!
A técnica AAA
Para estruturar os testes, nós utilizamos a técnica AAA:
- Arrange: Inicializar os objetos e declarar as variáveis.
- Act: Chamar os métodos de testes utilizando os parâmetros criados no primeiro passo.
- Assert: Verificar os resultados. Comparar os resultados com suas respectivas expectativas.
Tipos de testes
Unitários
Testes unitários são implementações de códigos que tem por objetivo testar unidades isoladas de funcionalidades. Ou seja, testes de unidade de código isolados. Para realizar testes unitários é necessário o acesso ao código que está sendo testado. Estes são os testes mais básicos, rápidos e fundamentais de serem feitos.
Integração
Testes de integração são importantes para verificar se as unidades funcionam juntas. Por mais que as unidades de código funcionem de forma correta, é necessário verificar se elas funcionam em conjunto.
End to End
Testes end to end, ou testes de sistema, são testes que verificam o comportamento e capacidades do produto ou sistema como um todo. São os testes mais próximos do que o usuário vai encontrar na utilização final. São testes que raramente são feitos por serem lentos e complexos.
Dublês de testes
Os testes unitários necessitam de dados para utilizar como exemplos para alguns métodos. Assim, devemos simular dados para enviar para os métodos nos testes.
Dublês de teste são utilizados para realizar uma simulação das dependências da unidade que será testada. Martin Fowler, lista os seguintes dublês de teste no seu artigo sobre TestDouble: Dummy, Fake, Stubs, Spies e Mocks.
Dummy
Este tipo de dublê é usado apenas para que a execução do teste seja possível, ou seja, não interage com a unidade testada, mas precisa existir para que o teste seja bem sucedido. A grande vantagem de utilizar este tipo de dublê é não precisar criar uma série de objetos que não serão testados.
Fake
Objetos que possuem implementação, porém com o objetivo de diminuir a complexidade e/ou tempo de execução de alguns processos para acelerar os testes, porém nunca usado em produção.
Stubs
Fornecem respostas prontas para as chamadas feitas durante o teste, geralmente não responde a nada fora do que está programado.
Spies
São mocks que também registram algumas informações com base em como eles foram chamados. Uma forma disso pode ser um serviço de e-mail que registra quantas mensagens foram enviadas, ou apenas para identificar se determinado método foi chamado, ou não. Geralmente é programado para capturar o método chamado e repassar para o objeto espionado. Sendo assim o método é efetivamente chamado.
Mocks
São objetos pré-programados com expectativa e forma como as chamadas são feitas, e o que espera receber. Os mocks verificam o contrato entre as classes. Se algo estiver fora do programado, geralmente lança exceção caso não esteja configurado. A ideia por trás dos mocks é de abstrair a lógica da classe dependente, ao mesmo tempo que garante que as interações ocorrem dentro do esperado.
Fim
Espero que este artigo tenha deixado claro os principais conceitos sobre TDD!
Te vejo na próxima!