Durante muito tempo testar/debugar JavaScript era uma tarefa árdua (infelizmente, em alguns navegadores, ainda é). Quem aí se lembra do tempo em que não existia Firebug, por exemplo? E o tamanho dos scripts? Um simples menu drop-drown possuía umas 1.500 linhas de código. Não existia jQuery ou qualquer outro tipo de framework. Tempos difíceis.
Hoje a tarefa do desenvolvedor é muito mais fácil. Para debug temos o já citado Firebug e o Developer Tools do Chrome, entre outros. Nos testes, além do Jasmine, outro framework bem legal é o QUnit. O Jasmine, por focar em BDD, possui uma sintaxe mais fluida. Quem programa em Ruby/Rails vai notar a enorme semelhança com a ferramenta RSpec.
Nos exemplos vou utilizar uma versão modificada do Jasmine, jasmine-jquery. Ela possui alguns métodos próprios para o framework além de funções para carregar fixtures (templates).
Baby steps
Começar a trabalhar com uma cultura de testar sempre antes de desenvolver é bem difícil, principalmente para quem já está acostumado a programar antes e testar depois (manualmente). Comece devagar, sem medo. No início as coisas serão um pouco confusas, mas depois de adotar essa prática, você vai se perguntar como era possível programar sem testes.
O que testar e que testes escrever? Isso também vem com o tempo. Comece testando uma ou outra funcionalidade principal de um aplicativo já existente. Entenda como funciona a sua ferramenta de testes. Depois de um tempo comece a acreditar e confiar na sua intuição.
Procure sempre utilizar um conjunto variado de possibilidades, desde as mais óbvias até as mais inusitadas. Pense nos diferentes contextos, em tudo que interage com seu aplicativo: navegadores, sistemas operacionais, usuários, dados de entrada, scripts de terceiros etc.
Testes não evitam que seu software, uma vez finalizado, tenha bugs; não são a solução para todos os seus problemas de deploy, mas facilitam bastante essas etapas.
Red → Green → Refactor
O padrão básico a ser seguido é o seguinte:
- Escreva o teste — naturalmente, ele vai falhar;
- Escreva, sem se preocupar muito com qualidade, o código mais simples, que faça o teste passar;
- Reescreva seu código, implementando melhorias de performance, escalabilidade e removendo duplicidades.
Configurando o jasmine-jquery
Baixe a última versão do jasmine-jquery e vamos começar com nossos primeiros testes. A estrutura de pastas do nosso aplicativo deve ficar da seguinte maneira:
[cce lang=”xml”]- /
– /tests
– /lib
– /spec
– /fixtures
– /suites
– saudacao-spec.js
– /vendor
– SpecRunner.html
– tableless.js
[/cce]
Note que criamos um diretório “tests” onde ficarão todos os arquivos dos nossos testes, incluindo a biblioteca Jasmine. O arquivo SpecRunner.html é o responsável por executar e exibir os resultados dos testes, basta abri-lo no navegador. Dentro do diretório “tests/spec” ficarão nossos conjuntos de testes (suites) e nossos templates HTML (fixtures).
No início do SpecRunner ficam as chamadas para os testes:
[cce lang=”javascript”][/cce]
Você também pode incluir aqui qualquer javascript personalizado necessário para os testes. No nosso caso vamos incluir o arquivo tableless.js, que fica na raiz do nosso aplicativo.
[cce lang=”javascript”][/cce]
Nosso primeiro teste
Vamos supor o seguinte: uma página deve exibir uma mensagem de boas-vindas para o usuário que varia de acordo com o horário. De 5 da manhã ao meio-dia, exibe “Bom dia!”; de meio-dia até 6 da tarde, exibe “Boa tarde!”; de 6 à meia-noite, exibe “Boa noite!”; e, por fim, de meia-noite até 6 da manhã exibe “Dormir é para os fracos!”. A mensagem fica sempre dentro de um elemento div com id “mensagem”. Os horários vão desde a hora inicial (incluída) até a hora final. A função pode receber como parâmetro uma hora no formato hh:mm ou, caso não receba nada, exibe a mensagem de acordo com a hora do usuário.
Esse é um bom começo para seus testes, tente escrever, em um parágrafo, o que deve ser executado e o que é esperado. Trabalhe sempre com um pedaço de papel por perto, para rascunhos.
Vejamos como ficaria o desenho inicial dos nossos testes. Crie o arquivo /tests/spec/suites/saudacao-spec.js e digite o seguinte código:
[cce lang=”javascript”]describe(‘Exibição da mensagem de boas-vindas’, function(){
beforeEach(function(){
setFixtures(‘’);
this.mensagem = $(‘#mensagem’);
});
afterEach(function(){
this.horas = [];
});
it(“Deve exibir ‘Bom-dia!’ entre 5:00 e 11:59”, function(){
});
it(“Deve exibir ‘Boa-tarde!’ entre 12:00 e 17:59”, function(){
});
it(“Deve exibir ‘Boa-noite!’ entre 18:00 e 23:59”, function(){
});
it(“Deve exibir ‘Dormir é para os fracos!’ de 00:00 a 04:59”, function(){
});
it(“Deve exibir, por padrão, a mensagem de acordo com a hora do cliente”, function(){
});
});[/cce]
Na raiz do aplicativo, crie um arquivo chamado “tableless.js”. Nele nós escreveremos nossa função para exibir a mensagem de acordo com a hora. Sua estrutura inicial é a seguinte:
[cce lang=”javascript”]function saudacao(hora_atual){
var hora;
}[/cce]
Os conceitos básicos dos nossos testes giram em torno de três funções: describe, it e expect. Outras funções úteis, mas que nem sempre estarão presentes, são as funções beforeEach e afterEach. Também trabalhamos com uma função do jasmine-jquery, a setFixtures — falarei mais sobre ela na parte 2, por enquanto você só precisa saber que ela define templates/elementos no DOM.
-
describe — representa um conjunto de testes/comportamentos. Podem existir situações dentro de situações, por exemplo:
[cce lang=”javascript”]describe(‘Login’, function(){
describe(‘Sucesso’, function(){
});
describe(‘Falha’, function(){});
});[/cce]
- it — define um teste, uma ação. Por exemplo, “It should validate the username”, ou, “Deve validar o nome do usuário”, seria uma boa situação para nosso teste de login.
- expect — espera que alguma variável ou retorno de função seja igual a alguma coisa, ou verdadeiro, ou falso etc. Nos testes acima utilizamos apenas um tipo de validação, o matcher “toEqual”. Todo o teste deve ser chamado com a função expect, utilizando um dos matchers disponíveis (mais sobre eles na parte 2). Os principais são: toEqual, toContains, toBeTruthy e toBeFalsy.
- beforeEach — executada antes de cada teste dentro de um conjunto, muito útil para configurar elementos.
- afterEach — executada depois de cada teste dentro de um conjunto, ideal para reiniciar variáveis.
Escrevendo os testes
Agora chegou a hora de preencher os nossos testes. Os testes que verificam a mensagem entre uma hora e outra seguirão um mesmo padrão. Dado um conjunto de horas válidas, a função saudacao deve retornar a mensagem correta.
[cce lang=”javascript”]it(“Deve exibir ‘Bom-dia!’ entre 5:00 e 11:59”, function(){
this.horas = [’05:00′, ’09:33′, ’10:22′, ’11:59′];
for(i in this.horas){
saudacao(this.horas[i]);
expect(this.mensagem.text()).toEqual(‘Bom-dia!’);
}
});[/cce]
O teste que valida o retorno da função saudacao sem passagem de parâmetro busca a hora do cliente e exibe a mensagem de acordo com ela.
[cce lang=”javascript”]it(“Deve exibir, por padrão, a mensagem de acordo com a hora do cliente”, function(){
var data = new Date;
data.setTime(data.getTime());
var hora = data.getHours();
saudacao();
var texto = this.mensagem.text();
if(hora < 5)
expect(texto).toEqual(‘Dormir é para os fracos!’);
if(hora < 12)
expect(texto).toEqual(‘Bom-dia!’);
else if(hora < 18)
expect(texto).toEqual(‘Boa-tarde!’);
else
expect(texto).toEqual(‘Boa-noite!’);
});[/cce]
Nesse momento, abrindo o SpecRunner no navegador, todos os nossos testes estarão falhando. Lembra da nossa regra? Red (falhou), Green (passou) e Refactor.
A função saudacao()
Abaixo segue a minha implementação da função saudacao(). Não me preocupei muito com repetições e performance. Meu objetivo principal era fazer o teste passar.
[cce lang=”javascript”]function saudacao(hora_atual){
var hora;
if(typeof hora_atual == ‘undefined’){
var data = new Date;
data.setTime(data.getTime());
hora = data.getHours();
}else{
hora = hora_atual.split(‘:’);
hora = parseInt(hora[0].replace(/^0/, ”));
}
if(hora < 5)
$(‘#mensagem’).text(‘Dormir é para os fracos!’);
else if(hora < 12)
$(‘#mensagem’).text(‘Bom-dia!’);
else if(hora < 18)
$(‘#mensagem’).text(‘Boa-tarde!’);
else
$(‘#mensagem’).text(‘Boa-noite!’);
}[/cce]
Atualizando nossa SpecRunner veremos agora que todos os testes estão passando. Você pode (e deve) desenvolver aos poucos. Valide um teste de cada vez. Desenvolva com calma.
Melhorando nosso código
Como de praxe, vou deixar um dever de casa para vocês: a etapa de refactoring. O que podemos melhorar na nossa função saudacao()? E se o usuário passar uma hora atualmente inválida para nossa função, como “meio-dia”, “nove horas” etc. E se quisermos mudar o id do elemento #mensagem? E se quisermos exibir a saudação em mais de um elemento? Podemos melhorar algum nome de variável? Podemos reduzir o tamanho da nossa função? Nossa função está cumprindo seu objetivo? Tem algum código/texto repetido? E nossos testes? Estão cobrindo tudo? Prevendo todos os cenários?
O que não falta é opção para um bom refactoring.
Na segunda parte veremos a função spy, além de mais sobre fixtures, matchers personalizados e outros recursos avançados.
Referências
- Código fonte dos exemplos deste artigo
- SpecRunner do exemplo rodando no github
- Jasmine BDD
- jasmine-jquery
- BDD segundo a Wikipedia