DEV Community

Adriana Ferreira Lima Shikasho
Adriana Ferreira Lima Shikasho

Posted on • Updated on

 

[pt-BR] Conheça a melhor forma de escrever testes no seu front-end - #2

Como vimos na Parte 1, testar detalhes de implementação no front-end pode ser uma armadilha que pode acabar mascarando erros gerando falso-positivos ou falso-negativos ou até mesmo atrapalhando a produtividade por serem muito frágeis.

Vamos então abordar de maneira prática um caminho para se pensar em testes no front-end de forma que eles se tornem mais confiáveis e com mais qualidade.

O que será abordado:

  • Testando Casos de Uso
  • Cobertura de Código
  • Cobertura de Casos de Uso
  • Quando a Cobertura de Código oculta os Casos de Uso

Testando Casos de Uso

"Quanto mais seus testes se assemelham à forma como seu software é usado, mais confiança eles podem lhe dar." - Kent C. Dodds

Essa é a natureza dos testes de casos de uso, pensar em escrever testes que atendam a forma que nossos dois usuários** (usuário-final e usuário-desenvolvedor) usam e interagem com nossa aplicação.

**ver Detalhes de implementação da parte 1

Escrevemos testes para ter certeza de que nosso aplicativo funcionará quando o usuário os usar e principalmente melhorar a confiança da aplicação.

"Pense menos no código que você está testando e mais nos casos de uso que o código suporta." - Kent C. Dodds

Cobertura de Código

Cobertura de código é a métrica que nos mostra quais linhas do nosso código estão rodando durante o teste. Vamos utilizar esse código como exemplo:

function verificaArray(array) {
  if (Array.isArray(array)) return array
  else if (!array) return []
  else return [array]
}
Enter fullscreen mode Exit fullscreen mode

Sem incluir teste para essa função, o relatório vai indicar que 0% de Cobertura de código nos dando a ideia de quanta parte do código ainda falta cobrir.

O relatório de Cobertura de código não diz exatamente qual a importância da função verificaArray ou nem mesmo diz os casos de uso que ela atende.

Mas quando consideramos a aplicação inteira, mesmo o relatório de Cobertura de código não nos dando uma a visão completa, ele nos ajuda a identificar quais testes poderiam ser adicionados para atender os casos de uso da aplicação.

Cobertura de Casos de Uso

A Cobertura de Casos de Uso nos diz quantos casos de uso nosso teste atende. Voltando ao código de exemplo, podemos identificar o primeiro caso de uso da função verificaArray:

  • "Retorna um array se o argumento for um array".

Obs: Há uma convenção de escrever os títulos dos testes em inglês, entretanto neste estudo, vou escrevê-los em português-BR por uma questão didática, beleza?

test('Retorna um array se o argumento for um array', () => {
  expect(verificaArray(['Elefante', 'Girafa']))
  .toEqual(['Elefante', 'Girafa'])
})
Enter fullscreen mode Exit fullscreen mode

E com esse teste escrito, nosso relatório de Cobertura de Código vai parecer com algo do tipo:

function verificaArray(array) {          // coberto
  if (Array.isArray(array)) return array // coberto
  else if (!array) return []
  else return [array]
}
Enter fullscreen mode Exit fullscreen mode

Assim, se olharmos para as linhas que faltam, vamos concluir que são mais dois casos de Uso que faltam ser cobertos:

  • "Retorna um array vazio se o argumento tiver um valor falso"
  • "Retorna um array se o argumento não for um array e não for falso"

Vamos adicionar os testes para esses casos de uso, e ver como isso afeta a Cobertura de Código

test(
'Retorna um array vazio se o argumento tiver um valor falso', () => {
   expect(verificaArray()).toEqual([])
})

test(
`Retorna um array se o argumento não for um array e não for falso`,
() => {
  expect(verificaArray('Leopardo')).toEqual(['Leopardo'])
})
Enter fullscreen mode Exit fullscreen mode

Cobertura:

function verificaArray(array) {          // coberto
  if (Array.isArray(array)) return array // coberto
  else if (!array) return []             // coberto
  else return [array]                    // coberto
}
Enter fullscreen mode Exit fullscreen mode

Ótimo! Agora temos 100% de cobertura de código, mas também 100% de cobertura de casos de uso e podemos confiar que, enquanto não mudarmos os casos de uso dessa função, nossos testes vão continuar passando.

Quando a cobertura de código oculta os casos de uso

As vezes, o relatório de Cobertura de código pode indicar 100% de cobertura, mas não 100% de cobertura de casos de uso.

Esse é o motivo que é bom ter a prática de pensar em todos os casos de uso possíveis antes mesmo de começar a escrever os testes.

Por exemplo, vamos imaginar que a function verificaArray foi implementada desse jeito:

function verificaArray(array) {         
  if (Array.isArray(array)) return array
  else return [array].filter(Boolean)                   
}
Enter fullscreen mode Exit fullscreen mode

Com isso, nós temos 100% de cobertura com apenas os 2 casos de uso:

  • Retorna um array se o argumento for um array
  • Retorna um array se o argumento não for um array

Entretanto, se olharmos para o relatório de Cobertura de caso de uso, podemos perceber que está faltando um caso de uso:

  • Retorna um array vazio se o argumento tiver um valor falso

E isso é um mau sinal, pois mesmo se o usuário user a função verificaArray sem passar nenhum argumento, os testes vão continuar passando, mas o código vai quebrar.

E essa é justamente uma das razões que implementamos testes, garantir que o código continue atendendo aos casos de uso que desejamos incluir.

Por exemplo, se um outro dev remover o .filter(Boolean), os testes ainda continuam passando, mas qualquer código que dependia do resultado da function como sendo "falso", vai quebrar.

👉 Continue lendo na parte 3


Referências:
"How to know what to test" - Kent C. Dodds
https://kentcdodds.com/blog/how-to-know-what-to-test
"Avoid the Test User" - Kent C. Dodds
https://kentcdodds.com/blog/avoid-the-test-user
"Testing Implementation Details" - Kent C. Dodds
https://kentcdodds.com/blog/testing-implementation-details

Top comments (0)

An Animated Guide to Node.js Event Loop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.