Olá pessoal. Tudo certo?!
Javascript cresceu muito em importância nos últimos anos. No passado, era percebida como uma linguagem simplificada, para programação “simples” de páginas web. Hoje, é vista como uma linguagem poderosa, rodando tanto no cliente quanto no servidor.
Por causa do “crescimento” do Javascript, é natural pensar em alternativas para o desenvolvimento de testes de unidade automatizados.
No post de hoje, apresento alternativas para desenvolvimento e execução de testes de unidade para javascript, tanto no browser (usando o próprio QUnit), como fora dele (com um pequeno utilitário que desenvolvi).
Framework para testes de unidade
A escolha de um bom framework para testes de unidade pode simplificar muito a escrita de testes de unidade.
Há várias opções disponíveis. Entre as que conheço, tenho clara preferência pelo QUnit.
QUnit foi desenvolvido junto ao projeto jQuery. Hoje, é o framework de testes padrão para todos os projetos do grupo, incluindo o core do jQuery, jQueryUI, jQueryMobile, entre outros.
A DSL desenvolvida é bastante simples e intuitiva. Você encontra uma documentação bem completa no site oficial do projeto.
Nossos testes (cenário de exemplo)
Para ilustrar melhor nossos exemplos. Escrevi algum código e algum teste. Observe:
Comecemos pelo códigos que desejamos testar (dahn.js)
var isOdd = function(number) { return true; }; var computeGreatestCommonDivisor = function() { return 0; };
Como pode perceber, trata-se de uma implementação (incorreta) de um método para ver se um número é impar e outro para calcular o máximo divisor comum (MDC) para um conjunto de números.
Agora, os testes, escritos conforme DSL do QUnit.
module("Module 1"); test("isOdd", function() { expect(2); ok(isOdd(3), "passing 3, returns true."); ok(!isOdd(3), "passing 4, returns false."); }); test("computeGreatestCommonDivisor", function() { equals(computeGreatestCommonDivisor(8, 12), 4, "passing 8 and 12, returns 4."); });
Acho que o código é “auto-explicativo”.
Rodando os testes no browser
Já temos nossos testes, mas como executar?! Se for no browser, com QUnit, é muito simples. Observe:
Dahn tests
Como pode ver, uso os scripts (QUnit, jQuery) e estilos (QUnit), diretamente do CDN. Se preferir, você pode baixar esses arquivos.
“Executando” o html, temos:
Como indicado, dois dos três testes escritos falharam.
Rodando os testes fora do browser
Já conseguimos rodar os testes no browser. Agora, vamos ver como fazer isso fora.
Para conseguir isso, escrevi um “runner” para node.js (iniciei uma série para explicar o desenvolvimento de node.js). Observe:
Como pode ver, solicito que o usuário forneça o nome do arquivo com o script a ser testado e o nome do arquivo com o teste.
Esse utilitário altera o código de saída (ERRORLEVEL) para o sistema operacional para 0, caso todos os testes falhem; e para 1, caso todos os testes passem. Assim, pode ser usado em combinação com um sistema de build.
Como foi escrito QUnit-run
QUnit é bastante extensível. É relativamente fácil expandir o framework.
Usando node.js, temos a possibilidade de executar, facilmente, código Javascript fora do browser.
Require dos módulos necessários.
var qunit = require("./qunit").QUnit, fs = require("fs"), exitcode = 0;
Perceba que estou utilizando o script comum do QUnit como módulo aqui. Para isso, fiz o download do arquivo e o coloquei na mesma pasta onde está o runner.
“atalhos” para impressão de mensagens
Como pode ver nos screenshots, utilizo:
- vermelho para testes que falham;
- verde para testes que passam;
- branco (em realce) para indicar os módulos.
Para fazer isso, escrevi alguns “atalhos”. Observe
var print = console.log; var print_fail = function (msg) { print("\33[31m\33[1m" + msg + "\33[0m"); }; var print_sucess = function (msg) { print("\33[32m\33[1m" + msg + "\33[0m"); }; var print_highlight = function (msg) { print("\n\33[1m" + msg + "\33[0m"); };
Utilizei sequências de escape ansi para gerar saídas coloridas no console.
“watch” para contabilizar o tempo total de execução.
Acho muito importante que testes “rodem” rapidamente. Por isso, acho fundamental medir o tempo total de execução dos testes.
Observe:
var stopWatch = { startTime: null, stopTime: null, start: function () { this.startTime = new Date(); }, stop: function () { this.stopTime = new Date(); }, elapsedSeconds: function () { return (this.stopTime.getMilliseconds() - this.startTime.getMilliseconds()) / 1000; } };
A lógica é bem simples. Mas, suficiente!
Extensão do QUnit
QUnit pode ser facilmente expandido. Observe minha implementação:
(function () { qunit.init(); qunit.moduleStart = function (data) { print_highlight(data.name); }; qunit.moduleDone = function (data) { print("\n" + data.failed + " failed. " + data.passed + " passed. " + data.total + " total." ); }; qunit.testStart = function (data) { print("\n " + data.name); }; qunit.testDone = function (data) { print("\n " + data.failed + " failed. " + data.passed + " passed. " + data.total + " total." ); }; qunit.done = function (data) { stopWatch.stop(); print("\nFinished in " + stopWatch.elapsedSeconds() + " seconds."); if (data.failed > 0) { exitcode = 1; } }; qunit.begin = function() { stopWatch.start(); }; qunit.log = function (data) { var p = data.result ? print_sucess : print_fail, t = " " ; p("\n██" + t + data.message ); if (data.actual !== data.expected) { p("██" + t + "Actual = " + data.actual); p("██" + t + "Expected = " + data.expected); } }; } ());
Como pode ver, há funções para monitorar:
- Início (begin) e conclusão (done) de todo o teste;
- Início (moduleStart) e conclusão (moduleDone) de um “module”;
- Início (testStart) e conclusão (testDone) de um “test”;
- execução dos asserts (log)
“Disparador” do teste
A última parte da nossa pequena solução é o “disparo” propriamente dito dos testes. Observe:
if (process.argv.length < 4) { print_fail("Use: node qunit-run < script-file > < test-file >"); exitcode = 2; } else { eval(fs.readFileSync(process.argv[2], "utf-8")); eval("with (qunit) {" + fs.readFileSync(process.argv[3], "utf-8") + "}"); qunit.begin(); qunit.start(); } process.exit(exitcode);
Perceba que faço uma verificação muito simples dos argumentos; Carrego os arquivos indicados (com eval); e, no final, encerro o processo com o código de saída.
A solução é bem simples. Por isso, criei um repositório para o projeto no GitHub para que você possa me ajudar a ampliar e melhorar.
Era isso.
04/12/2011
Cara muito bom, já estou pegando o código aqui x)
Parabéns pelo post
08/12/2011
Show de bola Elemar
Parabéns e obrigado pelo post!