Introdução a testes unitários no desenvolvimento de software
Tags: testing, unittest, node, testdev
Published: 2024-03-22 19:10:03

## O que são testes no desenvolvimento de software

Testar o projeto é fundamental para garantir que as funcionalidades dentro do sistema estão de acordo com os resultados buscados pelo usuário final. Os testes servem simplesmente para minimizar e encontrar o maior número de falhas possível e ajudam o programador a criar vários contextos de utilização. Dessa forma, é uma boa prática adotar esse recurso como aliado no processo de desenvolvimento, evitando eventuais erros e poupando o tempo perdido tentando encontrar falhas de forma manual na visão de um usuário final.

## Quais são os tipos de testes que podemos usar no processo

Podemos destacar os testes em diferentes níveis. Começando por testes que validam pequenas unidades individuais e ir escalando até chegarmos em testes que verificam fluxos mais complexos.

## Implementando testes unitários

Os testes unitários servem para validar pequenos componentes e funções a fim de verificar se o resultado final foi o esperado. 

Existem diferentes ferramentas em cada linguagem de programação que nos auxiliam com os testes. Por exemplo, destaca-se o pytest para testar scripts em python, o JUnit para testes em java, o PHPUnit para para testes em PHP e o jest para testes em node.js. Os frameworks citados não são as únicas ferramentas que podem ser usadas, claro. Existem diversas outras opções e até mesmo módulos nativos que podem nos auxiliar no desenvolvimento, como veremos no primeiro exemplo.

Observe um pequeno exemplo onde temos um projeto em node.js (v21.5.0) e temos um arquivo onde é dada uma função que precisa calcular a soma de todos os números incluídos em um array passado como parâmetro.

```js
// somaTotal.js

export default function somaTotal(elementos) {
  return elementos.reduce((valorAnterior, valorAtual) => {
    return valorAnterior + valorAtual;
  }, 0);
}
```

Agora vamos escrever o primeiro caso de teste usando módulos nativos do node.js e verificar se o resultado foi o esperado.

```js
// somaTotal.test.js

import test from "node:test";
import assert from "node:assert";
import somaTotal from "./somaTotal.js";

test("A soma total deve resultar em 15", () => {
  const resultado = somaTotal([1, 2, 3, 4, 5]);
  assert.strictEqual(resultado, 15); 
});
```

Observe que temos uma função chamada test (importada de node:test) que recebe como parâmetro, um texto e uma função anônima. A string passada deve ser entendida como uma descrição do teste que queremos realizar e dentro da função anônima vamos executar a função a ser testada, no caso _somaTotal_.

O retorno de _somaTotal_ será armazenado em resultado, observe que passamos um array com números de 1 a 5. Por fim, avaliamos se o resultado da soma total foi o esperado com a utilização da função _strictEqual_ dentro do módulo _assert_.
A função _strictEqual_ vai verificar se os dois valores passados são iguais.

Ao executar o script de teste obtemos uma resposta positiva nos dizendo que todos os testes passaram e não ocorreram falhas.

```$ node --test somaTotal.test.js```

Mas e se passarmos um número ao invés de um array como parâmetro para a nossa função _somaTotal_? e se passarmos um array não numerico? e se passarmos um parâmetro diferente do esperado?

Nesse caso, nosso teste irá falhar pois precisamos fazer as devidas validações dentro da nossa função _somaTotal_.

A partir desse pequeno exemplo podemos ir escalando nossos casos de teste para esta mesma função e escrever novos casos utilizando outras sintaxes de testes. Por exemplo, podemos verificar se passando outros valores no array recebemos o resultado esperado.

```js
import { describe, it } from "node:test";
import assert from "node:assert";
import somaTotal from "./somaTotal.js";

describe("Testando função somaTotal", () => {
  
  it (“Deve ser igual a 15”, () => {
    const resultado = somaTotal([1, 2, 3, 4, 5]);
    assert.strictEqual(resultado, 15); 
  });
  
  it (“Não deve ser igual a 10”, () => {
    const resultado = somaTotal([2, 3, 3]);
    assert.notEqual(resultado, 10); 
  });

});
```

Nesse exemplo utilizamos as funções _describe/it_ importadas de _node:assert_ para criarmos contextos dos nossos testes e organizarmos a estrutura.

Veja um exemplo de projeto utilizando os recursos nativos do node.js para criar testes unitários no link abaixo.

https://bitbucket.org/ianpatricck/nodejs-native-tests

Documentação útil e fontes

https://nodejs.org/docs/latest/api/assert.html
https://nodejs.org/docs/latest/api/test.html

Agora vamos utilizar a biblioteca Jest para testar nossos componentes e utilizar algumas funções a mais como apoio no resultado final.

O Jest é um famoso framework de testes em javascript utilizado na maioria dos grandes projetos. Ele possui um rico conjunto de funções para diferentes contextos e que nos ajudam a compreender casos de testes que falham.

Em primeiro momento, precisamos instalar a biblioteca no nosso ambiente. Para utilizarmos a biblioteca, siga os passos da documentação oficial a seguir.

`$ yarn add –dev jest`

ou

`$ npm install –dev jest`

Após a instalação, adicione o script para realizar os testes no arquivo `package.json`.

```json
{
    "scripts": {
       "test": "jest"
    }
}
```

documentação oficial: https://jestjs.io/pt-BR/docs/getting-started

Vamos começar com um simples exemplo, dessa vez vamos trabalhar com consulta, vamos criar uma função que resgata um usuário em um array de objetos que será o nosso “banco de dados” caseiro.

```js
// findUserById.js

const users = [
  { id: 1, name: "John"},
  { id: 2, name: "Linus" },
  { id: 3, name: "Sean" },
  { id: 4, name: "louis" },
];

export default function findUserById(id) {
  return users.find(user => user.id == id);
}
```

Nesse arquivo temos um array users que possui 4 usuários que serão usados para nossos testes, em seguida temos a função `findUserById` que será testada. Observe que a função recebe um id como parâmetro e retorna um objeto de usuário.

Vamos criar o primeiro teste que precisa retornar um usuário inexistente no array, um item indefinido.

```js
// findUserById.test.js

import findUserById from "./findUserById.js";

test("Deve retornar undefined", () => { 
  expect(findUserById(10)).toBeUndefined();
});
```

Execute no terminal o comando

`$ yarn test`

ou

`$ npm run test`

Observe que ao usarmos o Jest, temos o acesso a algumas funções especiais como `toBeUndefined`. que afirma que o retorno da nossa função precisa ser indefinido, já que passados como parâmetro o id 10, que não existe em nosso array.

Podemos criar vários testes para a função de consulta em nosso arquivo para visualizarmos diferentes situações.

```js
// findUserById.test.js

import findUserById from "./findUserById.js";

test("Deve retornar undefined", () => { 
  expect(findUserById(10)).toBeUndefined();
});

test("Deve retornar verdadeiro, definido", () => { 
  expect(findUserById(1)).toBeDefined();
  expect(findUserById(2)).toBeDefined();
  expect(findUserById(3)).toBeDefined();
  expect(findUserById(4)).toBeDefined();
});

test("Deve retornar o usuário", () => { 
  expect(findUserById(2)).toStrictEqual({ id: 2, name: "Linus" });
});
```

Caso o teste não passe, será revelado em qual caso e porque o teste falhou, de forma intuitiva e legível no terminal.

Algumas funções úteis para testes são fornecidas na documentação oficial
https://jestjs.io/pt-BR/docs/using-matchers

Também podemos usar a sintaxe _describe/it_ no Jest para superar nossos testes.

```js
// findUserById.test.js

import findUserById from "./findUserById.js";

describe("Testando a função findUserById", () => {
  
  it("Deve retornar true pois o id 2 existe no array", () => {
    
    const alreadyExists = findUserById(2);
    let result = false;

    if (alreadyExists) {
      result = true;
    }

    expect(result).toBeTruthy();

  });

  it("Deve ser falso pois o usuário de id 3 não se chama John", () => {
    
    const user = findUserById(3);

    expect(user.name).not.toBe("John");
  
  });

});
```

Com esses simples exemplos você pode usar como base para testar em funções que manipulam bancos de dados reais, lembre-se de usar um banco separado do que se usa em um ambiente real. Tenha sempre um banco de dados em memória para realizar seus testes.

## Testes unitários no front-end

É possível realizar nossos testes do lado cliente do nosso projeto, o Jest também pode ser usado junto a frameworks front-end onde podemos testar elementos do DOM, o funcionamento de alguns fragmentos do sistema como a renderização de componentes, a existência de algum elemento na página, a resposta de alguma requisição simulada (mock) de alguma API externa entre outros recursos. Alguns frameworks que usam o Jest como alternativa para testes podem ser encontrados na documentação oficial.

https://jestjs.io/pt-BR/docs/testing-frameworks

Outra biblioteca que nos auxilia com testes em front-end é a Testing Library.

https://testing-library.com/

Podemos escalar nossos testes unitários do lado cliente e ir implementando várias unidades até termos uma combinação de testes mais complexos, assim como é feito do lado servidor.

Testes mais complexos e que combinam testes unitários e que façam validações com o uso de banco de dados e classes mais complexas são conhecidos como testes de integração. Outro tipo de teste conhecido, que também pode ser utilizado no front-end, são os testes de ponta a ponta (e2e) que testam o sistema em um contexto geral, em um nível abstrato como se fosse um usuário. Geralmente esses testes não cobrem todo o sistema, pois implementar esses testes pode ser uma tarefa demorada.

Existem algumas ferramentas úteis para realizar os testes e2e como o Selenium, Cypress e o Puppeteer.

_Fique atento: existem outros tipos de testes que não foram citados aqui. Cabe a você leitor, se aprofundar melhor e conhecer outros métodos de garantir a segurança nos seus projetos_.

## Conclusão

Como você pôde perceber, podemos automatizar a etapa de testes no processo de desenvolvimento para evitar dor de cabeça mais tarde. A implantação dos testes unitários evita a maioria dos erros que podem ocorrer mesmo quando escalamos para testes de níveis superiores.

Os exemplos vistos neste texto são simples, foram utilizadas apenas as funções mais básicas que usamos em um ambiente real. Existem outros conceitos e ferramentas como a biblioteca Mocha, que também é uma ferramenta útil para testar projetos em node.js.

https://mochajs.org/

Não esqueça de ler as documentações oficiais para treinar e aprender novos métodos úteis que muitas bibliotecas de testes fornecem.

Obrigado!