JsHamcrest: um alívio à dor de se testar código JavaScript
Há não muito tempo atrás, escrever testes automatizados era coisa para poucos. Nem tanto por obstáculos técnicos, mas porque, na verdade, ninguém se importava muito com isso.
A boa notícia é que a importância dos testes automatizados vem aumentando com o tempo e hoje, felizmente, já não é mais necessário “vender” a idéia. O aumento absurdo de ferramentas destinadas a facilitar a aplicação desta prática é uma prova disso.
Falando em ferramentas, neste texto mostrarei o JsHamcrest, um projeto open source desenvolvido pela Destaquenet cujo objetivo é fornecer uma versão incrementada, em JavaScript, da biblioteca Hamcrest. Para quem não conhece, Hamcrest é uma biblioteca de matchers que permite que regras sejam definidas declarativamente para uso em outros frameworks e bibliotecas.
Para apreciar é preciso antes viver sem
Para dar uma idéia do quão útil JsHamcrest pode ser, segue abaixo um exemplo de suite de testes criado com QUnit, ainda sem apoio do JsHamcrest, onde testamos o funcionamento de algumas funcionalidades da classe Array:
$(document).ready(function(){ test("Literal", function() { var arr = []; ok(arr instanceof Array); equals(arr.length, 0); }); test("Reverse", function() { var arr = [1, 2, 3, 4, 5].reverse(); equals(arr.length, 5); // This assertion fails // equals(arr, [5, 4, 3, 2, 1]); // These assertions work, but in an ideal world you shouldn't do such a thing for (var i = 0, j = 5; i < arr.length; i++, j--) { equals(arr[i], j); } }); test("Slice", function() { var arr = [1, 2, 3, 4, 5, 6, 7].slice(2, 5); equals(arr.length, 3); // This assertion fails // equals(arr, [3, 4, 5]); // These assertions work, but in an ideal world you shouldn't do such a thing for (var i = 0; i < arr.length; i++) { equals(arr[i], i+3); } // Another example of complex assertion for (var i = 0; i < arr.length; i++) { ok(arr[i] >= 3 && arr[i] <= 5); } }); });
O código, mesmo sendo feio e confuso, funciona:
O código desta suite de testes está disponível para download.
(Muitas) Limitações
O primeiro teste, que verifica o funcionamento do literal [], foi muito simples de implementar até porque não há praticamente nada a ser testado. Mas não fique feliz; no mundo real, as chances de seus testes serem assim tão simples são muito baixas.
Isso começa a ficar evidente à partir do segundo teste, onde é preciso apelar para o bom (?) e velho for para conferir o conteúdo do array, uma vez que QUnit não os suporta em comparações de igualdade.
Obviamente a mesma coisa também acontece no terceiro teste. E para mostrar que a a sitiuação só tende a piorar, ainda no terceiro teste temos um outro exemplo de asserção (tão feio quanto) que verifica se todos os elementos do array estão num determinado intervalo.
Infelizmente, a “feiura” não é a única coisa que incomoda nesse código. Para ilustrar, suponha que o terceiro teste estivesse quebrado:
test("Slice", function() { var arr = [1, 2, 3, 4, 5, 6, 7].slice(2, 5); equals(arr.length, 3); // ... // Another example of complex assertion for (var i = 0; i <= arr.length; i++) { ok(arr[i] >= 3 && arr[i] <= 5); } });
Como esperado, ao rodar os testes, o relatório mostra a ocorrência de um erro. O problema é que esse relatório faz um péssimo trabalho ao tentar mostrar o que de fato falhou:
Uma forma de se amenizar este problema é passar um texto descritivo aos métodos ok() e equals():
ok(num1 >= 3 && num1 <= 5, "Expected num1 between 3 and 5"); equals(num2, 10, "Checking value of num2");
O problema é que, ao fazer isso, passa a ser necessário um certo cuidado para manter os textos descritivos em sincronia com o código das asserções.
Se QUnit é assim tão ruim…
…por que não o trocar por outra coisa?
Apesar de existirem várias alternativas ao QUnit, nenhuma delas parece ser capaz de resolver problemas como os mostrados aqui. Portanto, trocar de framework, por enquanto, não é a solução.
Mas nem tudo está perdido.
Viva melhor com JsHamcrest
O código mostrado anteriormente pode ser facilmente adaptado para usar JsHamcrest:
// Integrates JsHamcrest with QUnit JsHamcrest.Integration.QUnit(); $(document).ready(function(){ test("Literal", function() { var arr = []; assertThat(arr, instanceOf(Array)); assertThat(arr, empty()); }); test("Reverse", function() { var arr = [1, 2, 3, 4, 5].reverse(); assertThat(arr, hasSize(5)); assertThat(arr, equalTo([5, 4, 3, 2, 1])); }); test("Slice", function() { var arr = [1, 2, 3, 4, 5, 6, 7].slice(2, 5); assertThat(arr, hasSize(3)); assertThat(arr, equalTo([3, 4, 5])); assertThat(arr, everyItem(between(3).and(5))); }); });
Gostou?
A primeira coisa que salta aos olhos é a simplicidade do código. Basta uma olhada rápida para saber exatamente o que ele faz. Note que mesmo asserções mais complexas como as mostradas nos dois últimos testes são implementadas com facilidade.
O resultado:
O código desta suite de testes está disponível para download.
E não é só isso. Ao mesmo tempo em que temos um vocabulário riquíssimo de asserções, JsHamcrest também fornece informações detalhadas sobre as mesmas, numa linguagem fácil de entender. E isso tudo isso sem nenhum esforço!
Mais informações sobre a API podem ser encontradas no pacote de documentação do projeto, também disponível para download.
Mas, e quanto ao problema do relatório de erro? Vamos quebrar uma asserção e ver o que acontece:
test("Slice", function() { var arr = [1, 2, 3, 4, 5, 6, 7].slice(2, 5); assertThat(arr, hasSize(3)); assertThat(arr, equalTo([3, 4, 5])); assertThat(arr, everyItem(between(3).and(4))); });
O resultado:
Muito melhor! Basta olhar a mensagem de erro para descobrir o que está errado.
Posso usar JsHamcrest com outro framework?
Atualmente, JsHamcrest integra com cinco frameworks: JsTestDriver, JsUnitTest, jsUnity, YUITest e QUnit.
Outra coisa legal é que integração feita pelo JsHamcrest leva em conta as convenções definidas pelo framework escolhido. Veja, por exemplo, como ficaria o código se tivéssemos optado pelo YUITest em vez do QUnit:
// Integrates JsHamcrest and YUITest JsHamcrest.Integration.YUITest(); YAHOO.namespace("array"); YAHOO.array.TestCase = new YAHOO.tool.TestCase({ name : "Array tests", testLiteral: function() { var arr = []; Assert.that(arr, instanceOf(Array)); Assert.that(arr, empty()); }, testReverse: function() { var arr = [1, 2, 3, 4, 5].reverse(); Assert.that(arr, hasSize(5)); Assert.that(arr, equalTo([5, 4, 3, 2, 1])); }, testSlice: function() { var arr = [1, 2, 3, 4, 5, 6, 7].slice(2, 5); Assert.that(arr, hasSize(3)); Assert.that(arr, equalTo([3, 4, 5])); Assert.that(arr, everyItem(between(3).and(5))); } });
Mas meu framework predileto não está nessa lista…
JsHamcrest pode vir a suportar outros frameworks no futuro. Se seu framework predileto não está na lista dos atualmente suportados, entre em contato conosco.
E aí, o que achou?
Gostou? Odiou? Tem sugestões de melhorias? Gostaríamos muito de ouvir sua opinião!



