Executando verificação de segurança...
8

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.

piramide de testes
Pirâmide de testes - fonte: https://medium.com/creditas-tech/a-pir%C3%A2mide-de-testes-a0faec465cc2

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

Carregando publicação patrocinada...
4

Fala Carlos Vinicius! Muito massa seu post! Na mesma pegada de desenvolvimento testável e escrevendo uma arquitetura desacoplada, publiquei esse artigo sobre a utilização de um ORM no contexto testável e independente.
No meu desenvolvimento guiado por testes, gosto muito de utilizar o factory pattern para o gerenciamento de entradas da minha função/classe. EX:
Nesse mesmo contexto de teste de uma classe controller, gosto de configurar meu teste da seguinte maneira:

import { CreateUserController } from "./controller";

const makeHttpRequest = (props?: Partial<HttpRequest>): HttpRequest => ({
  name: "any_name",
  email: "any_email",
  password: "any_password",
  ...props,
});

test("it should return 400 if no name is provided", async () => {
  const createUserController = new CreateUserController();
  const request = makeHttpRequest({ name: undefined });
  const response = await createUserController.handle(request);
  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 request = makeHttpRequest();
    const response = await createUserController.handle(request);

    expect(response).toEqual({
      statusCode: 200,
      body: "ok",
    });
  });
});

No exemplo acima, a função makeHttpRequest monta a minha requisição com todos os parâmetros válidos, e se eu quiser sobrescrever algum parâmetro, eu passo na chamada da função, como estou fazendo no primeiro teste. Isso me ajuda a manter o teste mais legível e com menos repetições.

Da mesma maneira, no segundo teste, eu não preciso passar nenhum parâmetro, pois a função makeHttpRequest já monta a requisição com todos os parâmetros válidos.

Espero que ajude! Abraço!

1

Muito interessante, realmente deve ajudar muito na questão de não repetir os paramêtros a serem enviados.
Vou começar a utilizar nas minhas aplicações também, obrigado pela dica hehe.

Abração

1
1

kkkk, testes unitários são bem difíceis quando não estrutura bem o código. mas isso são dores que aprendemos a lidar com a prática, vamos vendo o que funciona e o que não funciona e isso ao longo do tempo vai se moldando e se tornando muito mais simples.

então não desista, testes são muuuito importantes para uma aplicação que queira ter um bom tempo de vida pela facilidade de manutenção que esses testes irão trazer ao longo do tempo.

1

eu realmente gostei do artigo e se puder criar un artigo sobre as outras camadas... principalmente a de servicos, ficaria feliz em ler :)

obrigada pelo conteudo

1
1

Deixa eu te perguntar, você tem alguma dica para fazer testes envolvendo mockar APIs de terceiros e comunicação com Redis?

Porque eu tenho um caso que preciso em um o nestjs que tem muitas etapas, muitas trechos código que precisam de todo um contexto no Redis para conseguir chegar no estado válido que eu quero testar.

1

Olá Carlos, preciso e quero começar certo, estou no inicio de front e quero entender mais e saber implementar testes unitários nos meus códigos, por onde eu começo? Documentação de como fazer? Valeu o post.

1

Olá, com certeza posso trazer algum post sobre testes no frontend. mas logo adianto que se você estiver muito no comecinho, ainda não começou a estudar react ou alguma lib de front mais moderna, ainda não está na hora de se preocupar com testes.

A necessidade de usar testes unitários vem com o tempo, a dor de criar algo novo e quebrar o que já estava funcionando ou apenas bugs pequenos que aparecem do nd, os testes ajudariam a evitar mas quando esse periodo chegar, você vai enxergar com clareza a importancia dos testes.

Mas estarei trazendo um conteúdo de testes unitários no ReactJS. a pegada é muito parecida com esse que trouxe mas com alguns detalhes um pouquinho mais chato.

Forte abraço e espero que esse conteúdo e os próximos auxilie você a construir sua carreira!!

1