Jasmine: escreva testes de unidade para seu código JavaScript!

Introdução

Você já parou para pensar a quantidade de código JavaScript que escrevemos atualmente?  Em nossas aplicações web, cada vez mais e mais escrevemos código em JavaScript. Seja no lado cliente, utilizando jQuery, AngularJS, React etc., seja no lado servidor com o Node.js, o JavaScript está cada vez mais presente. E você costuma escrever testes unitários para seu código JavaScript? Se você ainda não faz, está na hora de conhecer o Jasmine.

Todo código gerado precisa ter sua qualidade assegurada. E a forma mais precisa de garantir a qualidade de um código é escrevendo testes de unidade para ele. É aí que entra em ação o framework de testes Jasmine. O Jasmine é um framework para escrever testes para o seu código JavaScript. Ele é independente de navegador web e não precisa de outras bibliotecas adicionais para funcionar. Com isso, ele é ideal para testar código em qualquer lugar que rode um código JavaScript.

Neste post vou apresentar o Jasmine e mostrar como podemos escrever testes para o código JavaScript.

Configuração Inicial do Jasmine

A forma mais simples para começar a trabalhar com o Jasmine é fazer download da última versão da biblioteca. No momento em que escrevo este post, a última versão é a 2.5.0. Ela pode ser baixada nessa página (baixe o arquivo jasmine-standalone-x.x.x.zip).

Depois de baixar a biblioteca e descompactá-la no seu local de preferência, a estrutura  da parta é a mostrada a seguir.

- lib
|- jasmine-2.5.0
|- boot.js
|- console.js
|- jasmine.js
|- ...
- spec
|- PlayerSpec.js
|- SpecHelper.js
- src
|- Player.js
|- Song.js
- SpecRunner.html

A pasta lib contem os arquivos de biblioteca do Jasmine.
A pasta spec contem exemplos de testes unitários.
Já a pasta src contem os exemplos de códigos a serem testados. Assim, o arquivo PlayerSpec.js testa o código do arquivo Player.js.

O SpecRunner.html

O arquivo SpecRunner.html, que se encontra na raiz do projeto, é o responsável pela execução dos testes. O código dele está listado abaixo.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Jasmine Spec Runner v2.5.0</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.5.0/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-2.5.0/jasmine.css">

  <script src="lib/jasmine-2.5.0/jasmine.js"></script>
  <script src="lib/jasmine-2.5.0/jasmine-html.js"></script>
  <script src="lib/jasmine-2.5.0/boot.js"></script>

  <!-- include source files here... -->
  <script src="src/Player.js"></script>
  <script src="src/Song.js"></script>

  <!-- include spec files here... -->
  <script src="spec/SpecHelper.js"></script>
  <script src="spec/PlayerSpec.js"></script>

</head>

<body>
</body>
</html>

Basicamente o que esse arquivo faz é importar o código para execução dos testes. Ele importa os arquivos que fazem parte da biblioteca do Jasmine. Em seguida, ele importa os arquivos que terão seus códigos testados – o Player.js e o Song.js. Por fim são adicionados os arquivos specs com os testes unitários – o SpecHelper.js e o PlayerSpec.js.

Se você abrir esse arquivo em um navegador web, os testes serão executados e você verá a tela abaixo com o resultado da execução dos testes.

SpecRunner.html - Resultado da execução dos testes
SpecRunner.html – Resultado da execução dos testes

O resultado da execução do SpecRunner.html é um relatório dos testes executados, informando quantos foram executados e quantos destes falharam. Neste caso, foram executados 5 testes (ou specs como o Jasmine chama) e nenhum deles falhou – tudo ficou verde.

Entendendo o Jasmine

O Jasmine possui uma filosofia e uma sintaxe muito simples. Como todo framework de testes, ele segue o padrão preparação dos dados, execução do código a ser testado e verificação do resultado (asserções).

A função describe()

Uma suite de teste no Jasmine começa com uma chamada à função describe(). Essa função tem 2 parâmetros: o primeiro é o nome de sua suite de teste (esse nome vai aparecer no resultado da execução dos testes); o segundo parâmetro é uma função com código que implementa a suite de teste em si. Veja o código abaixo retirado do arquivo de testes exemplos do Jasmine, o PlayerSpec.js.

describe("Player", function() {
   // aqui são implementados os testes em si
});

A função it()

Os specs (termo utilizado pelo Jasmine para se referir aos testes a serem executados) são criados por meio da função it(). Assim como a describe(), esta função recebe 2 argumentos. O primeiro é o nome do teste a ser executado. O segundo argumento é a função de teste, onde fica o próprio código de teste em si: a preparação, a chamada do teste e a verificação do resultado. Veja o exemplo abaixo.

describe("Player", function() {
   
  it("should be able to play a Song", function() {
     // aqui fica o código de teste
  });

});

A função expect()

Por fim é dentro da função que é o segundo argumento de it() colocamos o código de teste. Chamamos a função a ser testada e verificamos o resultado dela por meio de expectations. Para isso utilizamos a função expect() encadeada com algum matcher. Veja o exemplo abaixo.

describe("Player", function() {
   
  it("should be able to play a Song", function() {
     var player = new Player();
     var song = new Song();
     player.play(song);
     expect(player.currentlyPlayingSong).toEqual(song);
  });

});

A linha em destaque é onde é feita a verificação. A função expect() recebe o valor atual de execução; a função toEqual() é um matcher (o Jasmine possui vários matchers pré-definidos) que recebe como argumento o valor esperado.

Agora que vimos as principais funções utilizadas para a escrita de testes no Jasmine, vamos fazer um exemplo utilizando esses conceitos em conjunto.

Exemplo prático: uma Pilha

Vamos escrever código de teste para a estrutura de dados conhecida como Pilha. Para os que não conhecem, a pilha é uma estrutura de dados que implementa o algoritmo Last In, First Out (LIFO). É como uma pilha de pratos organizada para ser lavada; os primeiros pratos a serem tirados para lavar são os que estão no topo da pilha.

A figura abaixo demonstra o funcionamento de uma pilha que implementa o LIFO. Veja que os elementos vão sendo inseridos sempre no topo da pilha (parte de cima da figura). E são removidos sempre do topo (parte de baixo da figura).

Funcionamento da pilha - LIFO
Funcionamento da pilha – LIFO

Assim, criei um arquivo chamado Stack.js, que implementa esta estrutura. O código está abaixo (todo o código encontra-se disponível aqui em meu GitHub).

function Stack() {
  var itens = new Array();
  
  return {
 
    push : function(item) {
     itens.push(item);
    },
 
    pop : function () {
     if(this.isEmpty()) {
       throw new Error("Pilha está vazia!");
     }
     return itens.pop();
    },
 
    peek : function() {
     return itens[itens.length - 1];
    },
 
    size : function() {
     return itens.length;
    },
 
    isEmpty : function() {
     return itens.length == 0;
    } 
 }; 
}

O código acima é uma classe JavaScript que implementa uma pilha utilizando um Array JavaScript (itens). Ele disponibiliza diversos métodos que são as operações disponíveis em uma pilha:

  • push: adiciona um novo elemento à pilha;
  • pop: retira e retorna o elemento do topo da pilha (o último elemento que foi adicionado);
  • peek: permite verificar o elemento que está no topo da pilha, mas sem tirá-lo de lá;
  • size: função auxiliar que permite verificar a quantidade de elementos presentes na pilha;
  • isEmpty: outra função auxiliar que retorna se a pilha está vazia.

Agora vamos escrever testes para esse código.

Nosso primeiro teste: verificando a implementação do algoritmo LIFO

Seguindo a filosofia do Jasmine, vamos criar um arquivo chamado StackSpec.js e colocá-lo na pasta spec do projeto. O primeiro código de teste que vamos escrever é o código que testa o comportamento do código pop(). Vamos ao código.

describe("Stack", function() {
 
 it("Deve implementar o LIFO (Last In, First Out) : a retirada é do elemento que está no topo.", function() {
   var pilha = new Stack();
 
   pilha.push(1);
   pilha.push(2);
   pilha.push(3);
   pilha.push(5);
 
   expect(pilha.pop()).toEqual(5);
 }); 

});

Conforme explicado anteriormente, tudo começa com a função describe(), onde definimos o nome do teste. Costumeiramente,  definimos como nome da suite o nome da classe ou do arquivo sendo testado – nesse caso “Stack”.

Dentro da função do describe(), criamos o caso de teste com a função it(). Colocamos como nome desse caso de teste o comportamento que esperamos: “Deve implementar o LIFO (Last In, First Out) : a retirada é do elemento que está no topo.”. Esses nomes são importantes, pois são eles que são exibidos na execução dos testes no SpecRunner.html, e devem deixar clara a intenção do teste.

Dentro da função que o it() recebe como argumento, está o código do teste. No caso criamos a pilha, adicionamos elementos a ela (por meio da função push()), e fazemos a verificação utilizando expect(): ao chamar pop(), esperamos que o último elemento adicionado seja o retornado – o elemento 5 nesse caso.

Para executar esse teste, modifiquei o arquivo SpecRunner.html, que vem na distribuição do Jasmine, adicionando o Stack.js e o StackSpec.js ao mesmo. Ele ficou assim:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Jasmine Spec Runner v2.5.0</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.5.0/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-2.5.0/jasmine.css">

  <script src="lib/jasmine-2.5.0/jasmine.js"></script>
  <script src="lib/jasmine-2.5.0/jasmine-html.js"></script>
  <script src="lib/jasmine-2.5.0/boot.js"></script>

  <!-- include source files here... -->
  <script src="src/Stack.js"></script>

  <!-- include spec files here... -->
  <script src="spec/StackSpec.js"></script>

</head>

<body>
</body>
</html>

Abaixo segue a tela com o resultado da execução do teste ao se abrir o SpecRunner.html em um navegador web.

Resultado da execução do teste da pilha
Resultado da execução do teste do algoritmo LIFO da Pilha

Para ver o teste falhar, modifique a asserção para que ele espere um elemento diferente de 5, por exemplo: expect(pilha.pop()).toEqual(2). O resultado de execução será algo similar à tela abaixo.

Teste com falha após a modificação do expect()
Teste com falha após a modificação do expect().

Observe que na imagem anterior o relatório de execução é claro quanto à falha ocorrida: retornou 5 quando o esperado era 2.

Mais um teste: verificando que pop() retira o elemento da pilha

Um outro teste que podemos fazer é verificar que a chamada à função pop() retira o elemento da pilha. Nesse caso, a quantidade de elementos existentes diminui de 1. O código de teste para esse caso é apresentado abaixo.

it("Chamar o pop() deve remover o elemento do topo da pilha.", function() {
  var pilha = new Stack();
 
  pilha.push(1);
  pilha.push(2);
  pilha.push(3);
  pilha.push(5);
 
  pilha.pop();
 
  expect(pilha.size()).toEqual(3);
});

A figura abaixo mostra o resultado da execução do SpecRunner.html após a adição desse novo caso a nossa suite de teste. Observe que agora temos 2 casos sendo executados.

jasmine-execucao-2-specs
Resultado da execução dos 2 testes criados

O código de teste StackSpec.js completo

Abaixo, apresento a implementação completa que fiz do StackSpec.js – que se encontra em meu GitHub.

Observe que o código dos casos de teste são similares aos já apresentados.

describe("Stack", function() {
 
 it("Deve saber que a pilha não está vazia", function() {
   var pilha = new Stack();
   pilha.push(1); 
   expect(pilha.isEmpty()).toBe(false);
 });
 
 it("Deve saber que a pilha está vazia", function() {
   var pilha = new Stack(); 
   expect(pilha.isEmpty()).toBe(true);
 });
 
 it("Deve implementar o LIFO (Last In, First Out) : a retirada é do elemento que está no topo.", function() {
   var pilha = new Stack(); 
   pilha.push(1);
   pilha.push(2);
   pilha.push(3);
   pilha.push(5);
 
   expect(pilha.pop()).toEqual(5);
 });
 
 it("O método peek() permite verificar o topo da pilha.", function() {
   var pilha = new Stack(); 
   pilha.push(1);
   pilha.push(2);
   pilha.push(3);
   pilha.push(5);
 
   var item = pilha.peek(); 
   expect(item).toEqual(5);
 });
 
 it("Chamar o pop() deve remover o elemento da pilha.", function() {
   var pilha = new Stack(); 
   pilha.push(1);
   pilha.push(2);
   pilha.push(3);
   pilha.push(5);
 
   pilha.pop(); 
   expect(pilha.size()).toEqual(3);
 });
 
 it("Chamar o peek() apenas verifica o topo da pilha, sem removê-lo.", function() {
   var pilha = new Stack(); 
   pilha.push(1);
   pilha.push(2);
   pilha.push(3);
   pilha.push(5);
 
   pilha.peek(); 
   expect(pilha.size()).toEqual(4);
 });
 
 it("Chamar o pop() em uma pilha sem elementos deve lançar uma exceção", function() {
   var pilha = new Stack(); 
   expect(function() {pilha.pop()}).toThrow(new Error("Pilha está vazia!"));
 })
 
}); 

Chamo atenção especial para o matcher utilizado no último caso de teste – que verifica se é lançado um erro quando chamamos pop() em uma pilha vazia. Veja que utilizei o matcher toThrow(), indicando que espero que seja lançado um erro nesse caso.

Recomendo fortemente a leitura da documentação para conhecer os matchers disponibilizados pelo Jasmine.

Conclusão

Nesse post apresentei como escrever testes unitários para o seu código JavaScript utilizando o framework de testes Jasmine.

Como pode ser visto nos exemplos apresentados, não é difícil escrever testes de unidade com o Jasmine – principalmente para quem já está familiarizado com a escrita de testes de unidade. A sua sintaxe é limpa, clara e não depende de nenhum outro framework JavaScript.

Com a quantidade de código que escrevemos hoje em JavaScript, não é mais aceitável que eles não sejam testados, pois precisamos garantir a sua qualidade. E o Jasmine veio para simplificar a tarefa de escrita dos testes. Use e abuse do framework, incorporando a prática de escrita de teste unitários JavaScript ao seu pipeline de testes.

Lembro, mais uma vez, que o código completo desse post está disponível em meu GitHub.

Abraços e até a próxima.

Gostou do Jasmine? Que tal integrá-lo em suas aplicações AngularJS? Clique aqui e veja como fazer isso!
  • rafaelblink

    Ótimo artigo, estou começando a estudar testes com Jasmine, nunca trabalhei com nenhum tipo de teste, seja front-end ou back-end e estou gostando muito pois a entrega dos artefatos sempre são com maior qualidade.

    • Obrigado @rafaelblink:disqus. A ideia é essa mesma, entrega de código com qualidade. E os testes automatizados te ajudam nessa tarefa: tanto na qualidade interna (do próprio código em si), como na qualidade externa – a do produto.

      Abraços.