Descubra como os testes automatizados de software podem garantir a qualidade e eficiência no desenvolvimento de sistemas com JavaScript.
Tempo de Leitura: 8 minutos
Introdução
Se você estuda ou trabalha com desenvolvimento de sistemas, é muito provável que já tenha ouvido falar sobre testes automatizados de software. Mas, você sabe de fato quais os objetivos desses tipos de testes de software, como funcionam e como aplicá-los na prática?
Neste artigo, explicarei os conceitos de testes automatizados e como utilizá-los, além de demonstrar o seu funcionamento com uma das linguagens mais famosas do mercado, o JavaScript.
O que são Testes de Software?
Para garantir o pleno funcionamento de um recurso ou funcionalidade em um software é imprescindível testá-lo, a fim de encontrar possíveis falhas, corrigi-las e entregar um produto que funcione exatamente como foi planejado.
Esses testes podem ser realizados de diferentes formas: uma delas é realizar os testes manualmente, método por método, recurso por recurso, com o intuito de verificar se o software está funcionando. Aqui, podemos realizar exatamente os passos que um potencial usuário realizaria. Mas, ao adicionarmos uma nova melhoria em um aplicativo, por exemplo, temos que testá-lo novamente, para identificarmos algum problema que possa ter surgido, certo?
Se todas às vezes que adicionarmos uma nova funcionalidade em nosso software, tivermos que testar cada um dos seus recursos manualmente, o processo de entrega do sistema tende a ficar mais dificultoso e demorado, principalmente em casos de sistemas grandes e complexos.
É aqui que entram os testes automatizados de software: além de desenvolvermos o próprio aplicativo, também podemos desenvolver scripts que percorrem as funcionalidades do nosso sistema e verifiquem se o comportamento dos recursos disponíveis está de acordo com o esperado nas especificações.
Os testes de software, sejam manuais ou automatizados, desempenham um papel crucial na validação do comportamento correto de funcionalidades, garantindo que o sistema funcione conforme planejado. Esse processo evita falhas e garante a entrega de um produto de alta qualidade. Além dos testes funcionais, é importante também validar a comunicação entre sistemas, como é feito nos testes de API. Para explorar esse aspecto mais técnico, confira nosso artigo sobre Testes de API e entenda como melhorar a integração entre seus serviços.
Testes Automatizados de Software
Para começarmos a entender um pouco mais sobre testes de software automatizados, vamos iniciar criando um pequeno sistema de compras em JavaScript. Ele terá como requisitos:
- O sistema deve receber o nome do produto;
- O sistema deve receber o valor do produto, que precisa ser maior que zero;
- O sistema deve receber o desconto do produto, que precisa ser menor ou igual ao valor do produto;
- O sistema deve possuir um método finalizar, que deverá retornar uma mensagem com os dados informados sobre o produto e o valor final da compra.
Com os requisitos acima, já conseguimos imaginar como nosso sistema de compras deve funcionar e quais erros deve retornar.
- Deveria lançar um erro “Produto inválido” ao tentar definir um nome de produto sem valor;
- Deveria lançar um erro “Valor inválido” ao tentar definir um valor menor ou igual a zero,
- Deveria lançar um erro “Desconto inválido” ao tentar definir um desconto menor que zero ou maior que um;
- Deveria lançar um erro “Pedido inválido” ao tentar finalizar compra, se não existirem o nome, valor e desconto do produto,
- Deveria mostrar a mensagem com o nome do produto, e o valor final, considerando o desconto. Exemplo: “Compra do produto XYZ finalizada no valor de 30 reais.”
Vamos começar criando uma classe que receberá o nome Compra que terá as seguintes propriedades: produto, valor e desconto, além do método finalizar, conforme o último dos requisitos informados anteriormente.
class Compra {
set setProduto (produto) {
this.produto = produto;
}
set setValor (valor) {
this.valor = valor;
}
set setDesconto (desconto) {
this.desconto = desconto;
}
finalizar () {
return `Compra do produto XYZ finalizada no valor de 30 reais.`;
}
}
Já possuímos a base da nossa classe criada, claro que ainda sem atender os requisitos informados, mas já temos uma noção da sua estrutura. Agora, vamos imaginar parte por parte como nós poderíamos utilizá-la e o como nós esperamos que ela se comporte.
Primeiramente, vamos criar um objeto compra com uma nova instância da classe Compra. Em seguida, podemos utilizar o setter setProduto para informarmos o nome do produto. Olha só como ficaria:
const compra = new Compra();
compra.setProduto = "Camiseta";
Esse é o caminho feliz e o que esperamos que aconteça. Porém, precisamos ter em mente que, assim como pontuado anteriormente, o sistema "deveria lançar um erro 'Produto inválido' ao tentar definir um nome de produto sem valor”, como nesses exemplos:
const compra = new Compra();
compra.setProduto = "";
compra.setProduto = null;
Testes Automatizados do Fluxo de Sucesso
E como podemos criar testes automatizados básicos para validar se a nossa classe está se comportando da forma que esperamos, tanto em casos de sucesso, como de erro?
Simples, podemos verificar se ao atribuirmos um nome com o compra.setProduto, a propriedade compra.produto possuirá o valor esperado. Se ele possuir, quer dizer que o nosso setter está funcionando, se não, quer dizer que há algum erro. Como precisamos testar inclusive se os erros que acontecem são esperados, vamos utilizar um try catch. Aqui vai um exemplo:
// Teste do fluxo de sucesso
try {
const produto = "Camiseta";
const compra = new Compra();
compra.setProduto = produto;
if (compra.produto === produto) {
console.log("O setProduto funcionou.");
} else {
console.log("O setProduto não funcionou.");
}
} catch (erro) {
if (erro.message === "Produto inválido") {
console.log("O setProduto retornou o erro esperado.");
} else {
console.log("O setProduto não retornou o erro esperado.");
}
}
// Output: O setProduto funcionou.
Testes Automatizados do Fluxo de Exceção
Os Testes Automatizados de Software do Fluxo de Exceção são um tipo de teste de software que foca em validar como o sistema se comporta diante de cenários inesperados ou condições de erro. Em vez de testar apenas os caminhos ideais (também chamados de fluxos de sucesso), esses testes garantem que o sistema consiga lidar corretamente com situações de falha ou exceção, como entradas inválidas, indisponibilidade de recursos ou erros de lógica.
Para forçarmos um erro, podemos duplicar o mesmo teste, e definir um produto como null, por exemplo.
// Teste do fluxo de exceção
try {
const produto = null;
const compra = new Compra();
compra.setProduto = produto;
if (compra.produto === produto) {
console.log("O setProduto funcionou.");
} else {
console.log("O setProduto não funcionou.");
}
} catch (erro) {
if (erro.message === "Produto inválido") {
console.log("O setProduto retornou o erro esperado.");
} else {
console.log("O setProduto não retornou o erro esperado.");
}
}
// Output: O setProduto funcionou.
Nesse caso, porém, o output ainda mostra que o compra.setProduto funcionou. O nosso teste está indicando que existe um problema na nossa classe, o setter não retornou o erro que esperávamos.
Isso acontece porque realmente ainda não colocamos essa validação na nossa classe. Ela deve ser assim:
class Compra {
set setProduto (produto) {
if (!produto) {
throw new Error("Produto Inválido");
}
this.produto = produto;
}
set setValor (valor) {
this.valor = valor;
}
set setDesconto (desconto) {
this.desconto = desconto;
}
finalizar () {
return `Compra do produto XYZ finalizada no valor de 30 reais.`;
}
}
Agora, sim, se rodarmos novamente o teste, o output deve ser como esperamos:
// Teste do fluxo de exceção
try {
const produto = null;
const compra = new Compra();
compra.setProduto = produto;
if (compra.produto === produto) {
console.log("O setProduto funcionou.");
} else {
console.log("O setProduto não funcionou.");
}
} catch (erro) {
if (erro.message === "Produto inválido") {
console.log("O setProduto retornou o erro esperado.");
} else {
console.log("O setProduto não retornou o erro esperado.");
}
}
// Output: O setProduto retornou o erro esperado.
A implementação da classe para atender totalmente os requisitos, poderia ficar da seguinte maneira:
class Compra {
set setProduto (produto) {
if (!produto) {
throw new Error("Produto inválido");
}
this.produto = produto;
}
set setValor (valor) {
if (!valor || valor <= 0) {
throw new Error("Valor inválido");
}
this.valor = valor;
}
set setDesconto (desconto) {
if (desconto > this.valor) {
throw new Error("Desconto inválido");
}
this.desconto = desconto;
}
finalizar () {
const valorFinal = this.valor - this.desconto;
return `Compra do produto ${this.produto} finalizada no valor de ${valorFinal} reais.`;
}
}
Testes Automatizados x Bugs
E, se por engano, alterássemos o código da nossa classe, e isso levasse a um bug? É aqui que entra o poder dos testes automatizados de software. Com eles, conseguimos identificar comportamentos do sistema que não esperávamos que tivessem tal resultado. Vamos a um breve exemplo, vou comentar todo o código interno do setter setProduto, que ficará da seguinte forma:
class Compra {
set setProduto (produto) {
/* if (!produto) {
throw new Error("Produto inválido");
}
this.produto = produto; */
}
set setValor (valor) {
if (!valor || valor <= 0) {
throw new Error("Valor inválido");
}
this.valor = valor;
}
set setDesconto (desconto) {
if (desconto > this.valor) {
throw new Error("Desconto inválido");
}
this.desconto = desconto;
}
finalizar () {
const valorFinal = this.valor - this.desconto;
return `Compra do produto ${this.produto} finalizada no valor de ${valorFinal} reais.`;
}
}
Com isso, se rodarmos novamente o primeiro teste que criamos, ele passará a ter um output diferente do esperado:
// Teste do fluxo de sucesso
try {
const produto = "Camiseta";
const compra = new Compra();
compra.setProduto = produto;
if (compra.produto === produto) {
console.log("O setProduto funcionou.");
} else {
console.log("O setProduto não funcionou.");
}
} catch (erro) {
if (erro.message === "Produto inválido") {
console.log("O setProduto retornou o erro esperado.");
} else {
console.log("O setProduto não retornou o erro esperado.");
}
}
// Output: O setProduto não funcionou.
Assim, o teste cumpriu o seu papel, ao testar o setter da classe que criamos e nos indicar que algo foi modificado no código que fez com que ocorresse um erro e o comportamento tivesse um resultado não esperado.
Aqui, conseguimos compreender o papel de um teste automatizado. Claro que nesse caso, é um exemplo bem simples, mas em sistemas grandes e complexos, é possível termos testes que validam cada parte do software e nos indicam se alguma alteração que realizamos levou ao aparecimento de bugs, o que nos dá maior tranquilidade durante o desenvolvimento.
Se você está interessado em se aprofundar ainda mais nos testes automatizados de software e tirar dúvidas sobre a implementação de diferentes cenários, como fluxos de exceção, o Fórum da Casa do Desenvolvedor é um ótimo espaço para trocar experiências e obter ajuda da comunidade de desenvolvedores. Lá, você pode discutir questões sobre testes de software e compartilhar desafios e soluções com outros profissionais da área.
Frameworks de Testes Automatizados de Software
Nos casos em que mostrei acima, escrevemos testes da forma mais básica possível. Porém, no dia a dia durante o desenvolvimento de software, possuímos algumas ferramentas e frameworks que nos auxiliam a escrever testes de forma mais rápida, assertiva e confiável, além de permitirem validarmos cenários ainda mais complexos em nossa aplicação.
Cada linguagem de programação pode possuir diversos frameworks de testes automatizados de software. No JavaScript, o mais comum deles é o Jest, que segundo sua própria documentação:
Jest é um framework de teste em JavaScript projetado para garantir a correção de qualquer código JavaScript. Ele permite que você escreva testes com uma API acessível, familiar e rica em recursos que lhe dá resultados rapidamente.
Com ferramentas como o Jest, torna-se possível criarmos testes mais completos e conseguimos obter relatórios de quais partes do código já testamos, quais partes ainda faltam a ser testadas e nos mostra de forma clara quais os problemas estão presentes no código e necessitam de atenção.
Que tal testar de forma automatizada, em poucos segundos ou minutos, 100% das linhas, métodos e funcionalidades do seu software? Fica aqui uma dica de ferramenta que pode te ajudar. Você também pode experimentá-la e reescrever os testes que criamos aqui nesse artigo!
Conclusão
Existem diversos tipos de testes de software e um dos mais utilizados e presentes do cotidiano dos desenvolvedores de software são os testes automatizados.
Como um grande aliado, os testes automatizados de software permitem uma grande economia de tempo a nós desenvolvedores, visto que precisamos escrevê-lo somente uma vez e o teste sempre está disponível para utilizarmos e testarmos se, após alguma alteração realizada, determinada parte do sistema continua funcionando como o esperado.
Além disso, é possível escrever testes de software automatizados de forma “rudimentar”, como fizemos aqui, utilizando os próprios recursos das linguagens, ou aproveitar da existência de bibliotecas (internas e externas) e frameworks disponíveis e empregá-los para facilitar a escrita dos testes.
Por fim, se você busca atestar que o seu software está funcionando da forma como deveria e que a implementação de novos recursos não causaram quedas na qualidade do software devido à presença de bugs, os testes automatizados de software são indispensáveis nas entregas realizadas durante o desenvolvimento.