Descomplicando: testes unitários
Quem sou?
Antes de comerçamos, devo me apresentar. Me chamo Carlos Vinicius e atualmente tenho 5 anos de experiência com desenvolvimento de software.
Motivação
Trabalhei em diversas empresas e percebi algumas dificuldades enfrentadas por elas e as dificuldes mais vísiveis foram arquiteturais e falta de testes.
Por conta disso, o dia a dia nessas empresas eram dramáticos, sempre tinhamos um pé atrás em fazer qualquer tipo de alteração pois nunca sabiamos se a aplicação iria continuar funcionando como deveria, pois poderíamos estar quebrando alguma regra ou mudando um retorno que outro trecho do código esperava receber.
Vendo esses problemas acontecerem continuamente, decidi estudar sobre testes para mudar a situação na qual a empresa se encontrava. Entretanto, para minha surpresa, testes foi uma grande barreira enfrentada, pois não sabia onde consumir conteúdos aprofundados sobre e não conhecia muito de padrões arquiteturais que ajudam a separar as responsabilidades e isso fazia com que fosse quase impossível testar as aplicações que eu construiua.
Por isso decidi escrever este artigo para descomplicar os testes unitários e deixarei uma sessão com conteúdos de apoio ao final.
OBS: Para facilitar a explicação sobre testes, utilizarei alguns casos como exemplo, porém esses códigos serão construidos usando typescript, mas lembrando que esse conteúdo serve para qualquer linguagem.
Descomplicando os testes
Existem uma infinidade de tipos de testes, os mais comuns são: E2E, integração e unitários. Nesse artigo falaremos somente de testes unitários, a base da pirâmide de testes.
Quando falamos de testes, dividimos opiniões. Muitos devs não conseguem viver sem eles enquanto outros ainda não conhecem os beneficios de utilizá-los, mas sem dúvidas testes são essenciais em qualquer aplicação.
"Tudo bem, Vinicius, entendi que testes são importantes, mas por onde eu começo?"
Assim como todos os exemplos que vemos em canais no youtube ou em outros artigos, testes são bem simples. vamos para o primeiro exemplo:
Observe o seguinte código:
// Função para formatar a string para maiúsculo
function toUpper(message: string) {
return message.toUpperCase()
}
Como poderíamos testá-lo?
test("should return message formated", () => {
const message = toUpper("anyMessage")
expect(message).toBe("ANYMESSAGE")
})
Esse primeiro teste é algo bem pequeno, mas com ele garantimos que mesmo que alterássemos a função, continuariamos com o retorno esperado.
Interessante, não?!. Mas quero abordar algo mais complexo e não ficar em um teste tão simples. Vamos construiur um cenário real de um controller (desenvolvimento web). Como poderíamos testar se ele está retornando um erro, caso um parâmetro obrigatório não esteja sendo informado?
Caso real de um teste
Vamos criar o controller para criação de usuário com as regras parecida com a do TabNews, onde dados como nome, email e senha são obrigários:
interface HttpRequest {
name: string
email: string
password: string
}
interface HttpResponse {
statusCode: number
body: any
}
class CreateUserController {
async handle(request: HttpRequest) {
if(!request.name || !request.email || !request.password)
return {
statusCode: 400
body: new Error("send all required fields")
}
return {
statusCode: 200,
body: "ok"
}
}
}
Como esse caso é apenas um exemplo, não vou entrar em mérito de injeção de dependência, mas saibamos que esse controller por si só não seria o suficiente, precisariamos chamar a camada de serviço para torná-lo funcional.
Este controller apenas deve retornar erro caso algum dado obrigatório não esteja sendo enviado na requisição.
Agora vamos testar nosso controller:
test("it should return 400 if no name is provided", async () => {
const createUserController = new CreateUserController()
const response = await createUserController.handle({
name: '',
email: '[email protected]',
password: 'anyPassword'
})
expect(response).toEqual({
statusCode: 400,
body: new Error("send all required fields")
})
})
test("it should return 400 if no email is provided", async () => {
const createUserController = new CreateUserController()
const response = await createUserController.handle({
name: 'anyName',
email: '',
password: 'anyPassword'
})
expect(response).toEqual({
statusCode: 400,
body: new Error("send all required fields")
})
})
test("it should return 400 if no password is provided", async () => {
const createUserController = new CreateUserController()
const response = await createUserController.handle({
name: 'anyName',
email: '[email protected]',
password: ''
})
expect(response).toEqual({
statusCode: 400,
body: new Error("send all required fields")
})
})
test("it should return 200 valid data is provided", async () => {
const createUserController = new CreateUserController()
const response = await createUserController.handle({
name: 'anyName',
email: '[email protected]',
password: 'anyPassword'
})
expect(response).toEqual({
statusCode: 200,
body: "ok"
})
})
Cada teste envia dados fictícios para simular o comportamento do controller e verifica se seu retorno está condizendo com o esperado.
Com esse código, conseguimos testar todos os casos de erros e ainda garantimos que o controller está conseguindo fazer sua função corretamente.
Com testes como estes, conseguimos garantir que mesmo se alterarmos algo que afete essa validação, eles acusarão o erro antes de chegarmos a fazer o commit do código com essas mudanças.
Conclusão
Viu como é simples criar testes? utilizar os testes tem muitas vantagens ao longo do tempo, como a facilidade de manutenção da base de código, diminuição de bugs e até mesmo auxiliar em refatorações.
Esse exemplo usado foi construído seguindo alguns conceitos que acho que não vale a pena explorar nesse artigo, caso queiram um conteúdo onde eu explique sobre outras camadas como a de serviço, podem deixar nos comentários que prepararei um material sobre isto
Obrigado por ter chegado até aqui e lembre-se, esteja sempre em aprendizado continuo!
Bons estudos!!!!
Links
- Meus contatos
Github
Linkedin - Matérial de estudo:
A pirâmide de testes
Uma ÚNICA Coisa Me Faz Programar “10x” Mais Rápido (De Verdade)
Seu próximo back-end Node com TESTES! (+ SOLID)