: : www.mundoj.com.br : : Caíres Vinicius ([email protected]): é desenvolvedor e consultor pela Caelum nas linguagens em Java, Ruby e Javascript. Entusiasta de usabilidade para Web e Javascript Server-side. Lucas Souza ([email protected]): é bacharel em Engenharia da Computação pela Universidade de Ribeirão Preto, possui a certificação SCJP e trabalha com Java há 4 anos. Atualmente é desenvolvedor e instrutor pela Caelum. Entusiasta de metodologias ágeis e editor-chefe do InfoQ Brasil. Testando seus códigos JavaScript: A importância e a facilidade de criá-los Aprenda como escrever testes de unidade para o lado cliente da sua aplicação A linguagem JavaScript tem se tornado a principal nas aplicações conhecidas como rich applications ou aplicações web 2.0. Porém ainda não tem recebido a devida atenção quando o assunto diz respeito a testes automatizados. No artigo será mostrado na prática a importância e a relevância de se criar testes para seus códigos JavaScript, mostrando ferramentas que permitem a criação de simples testes de unidade, até a criação de mocks simulando dependências complexas. ódigo JavaScript tem se tornado cada vez mais frequente em aplicações web ricas, as ditas aplicações web 2.0. Bibliotecas como jQuery, Prototype, Mootools e outras facilitam muito o trabalho dos desenvolvedores. Porém o código muitas vezes acaba não recebendo a atenção necessária, e os problemas são descobertos ou pelos usuários das aplicações ou pelos desenvolvedores quando utilizam ferramentas de debugging no estilo do Firebug. Mesmo com essas ferramentas e com a correção de erros sendo feita frequentemente, não temos muito cuidado no processo de isolar determinados códigos assim como é feito quando nos referimos a códigos no server side. O código termina como uma big ball of mud, ou uma grande “bola de lama”, onde misturamos códigos que manipulam a árvore DOM, código de chamadas Ajax para o servidor. Quando 46 precisamos alterar estes códigos temos uma dificuldade imensa. Em relação a nossa aplicação, a primeira opção prioriza a funcionalidade, isto é, deve funcionar com ou sem javascript. Essa solução é conhecida como Graceful Degradation. Garantir que a aplicação funcione sem javascript vai gerar uma perda de usabilidade. O objetivo do artigo é mostrar que testes em códigos JavaScript são fáceis de fazer e hoje em dia existem muitas ferramentas que nos auxiliam em processos complexos, como a criação de mocks e stubs, além da integração destes testes com servidores de integração contínua. Com isso, obtemos códigos mais legíveis e coesos e, evidentemente, com menor número de bugs. Testes de unidade com QUnit Vamos primeiro simular um cenário no qual devemos calcular alguns impostos e mostrar o resultado para o usuário. Poderíamos resolver o problema de duas maneiras: •Uma requisição no server side para calcular nosso imposto e retornar a página preenchida com o valor. •Um código JavaScript que calcula o valor do imposto, sem nenhuma requisição feita no server side. Pensando em cada vez mais melhorar a usuabilidade e a experiência do usuário em relação a nossa aplicação, ou seja, pensando na questão do graceful degradation, a primeira opção é mais recomendada. Porém, é uma solução que possui um problema, que pode ser uma resposta lenta ao usuário. Neste caso, fazendo o cálculo através de um código JavaScript deixaria a aplicação mais rápida pelo fato de não ter que fazer uma requisição e esperar a resposta do servidor, evitando também tráfegos desnecessários pela rede. Quando não testamos, perdemos qualidade. Com JavaScript não é diferente, e se não testarmos, podemos colocar um valor errado que pode prejudicar a parte financeira que utiliza sua aplicação ou não exibir um elemento HTML que deveria aparecer e não aparece. O teste é também para garantir essa qualidade. Em nosso primeiro código, adotaremos a abordagem de TestDriven Development, que é uma excelente prática de desenvolvimento, e que já é seguida fortemente quando testamos códigos server side. Vamos começar pela criação de um teste e depois escreveremos o código javascript para que este teste passe. Utilizaremos uma ferramenta de testes chamada QUnit, que é muito semelhante ao JUnit utilizado para testes de unidade em Java. O QUnit é a ferramenta escolhida pelo conhecido projeto JQuery para efetuar os testes da sua implementação e também dos plugins existentes para ele. Mas também pode ser utilizado para testar códigos JavaScript que não utilizam JQuery, exatamente o que confere com o nosso cenário. Para criarmos nosso teste utilizando o Qunit, basta escrever o HTML da Listagem 1, e dentro da tag <script> colocar a função onload. Dentro da função onload, ficam as asserções que desejamos efetuar. Um detalhe é que neste arquivo incluiremos o arquivo impostos.js, que contém a função que desejamos testar. Também está incluso a importação de um arquivo css do próprio QUnit para fazer a formatação do HTML retornado. Na tag <body> são incluídos alguns elementos HTML onde o QUnit exibirá os resultados dos nossos testes. Nosso teste falha porque ainda não implementamos a função "calcula". Para calcular o valor com os impostos precisamos de três informações: valor, valor do imposto e uma taxa administrativa. No nosso caso, vamos fazer uma multiplicação e uma adição como fórmula para o cálculo do nosso imposto. Sabendo quais argumentos e qual a fórmula do cálculo do imposto, vamos implementar nossa função dentro do arquivo impostos.js, assim como feito na Listagem 2. Listagem 1. Criação do teste utilizando Qunit. <html> <head> <link rel=”stylesheet” href=”qunit.css” type=”text/css” media=”screen” /> <script type=”text/javascript” src=” qunit.js”></script> <script type=”text/javascript” src=”impostos.js”></script> <script> window.onload = function() { test(“calcula imposto corretamente”, function() { equals(5.7, Imposto.calcula(10.20, 0.5, 0.6)); }); } </script> </head> <body> <h1 id=”qunit-header”>QUnit example</h1> <h2 id=”qunit-banner”></h2> <h2 id=”qunit-userAgent”></h2> <ol id=”qunit-tests”></ol> </body> </html> Listagem 2. Criação do cálculo de impostos. Imposto.calcula = function(valor, imposto, taxa) { return (valor * imposto) + taxa; } Se executarmos em nosso browser favorito, o código presente na Listagem 1, perceberemos que ele nos retorna uma mensagem de erro, dizendo que nosso código falhou. Isto ocorre por causa do arredondamento do ponto flutuante do Javascript. Ao invés de retornar 5.7, nossa função retorna o valor 5.699999999999999. O que aconteceu no código acima é um problema que pode ser considerado crônico em relação a arredondamentos em linguagens de programação, por exemplo, a linguagem Java. A linguagem JavaScript também tem problemas com arredondamento de pontos flutuantes. Percebemos este problema apenas porque nosso código Javascript recebeu a devida atenção e criamos um teste de unidade para um cálculo que parecia simples, mas que poderia causar grandes transtornos. Mas como podemos resolver este problema? Existe alguma solução nativa do JavaScript? JavaScript possui uma biblioteca que faz algo similar ao BigDecimal: a classe BigNumber (http:// jsfromhell.com/classes/bignumber). Esta classe JavaScript resolve nosso problema e faz nosso teste passar, garantindo uma melhor 47 : : www.mundoj.com.br : : precisão em cálculos com pontos flutuantes. Basta alterarmos a implementação do código presente na função calcula. O código utilizando a classe BigNumber é mostrado na Listagem 3. Listagem 3. Cálculo de impostos utilizando BigNumber. Imposto.calcula = function(valor, imposto, taxa) { var valorComImposto = new BigNumber(valor). multiply(imposto); return Number(new BigNumber(taxa).add(valorComImposto)); } Utilizando a classe BigNumber e executando o teste novamente, ele passará. Porém existe outra maneira de corrigir nossa função utilizando o método toFixed() do próprio Javascript, que formata um número usando a notação de um ponto fixo. Utilizando o toFixed nosso código ficaria como o descrito na Listagem 4. Listagem 4. Cálculo de impostos utilizando toFixed. Listagem 5. Mock de Ajax com ScrewUnit e Smoke. <html> <head> <script src=”jquery-1.3.2.js”></script> <script src=”jquery.fn.js”></script> <script src=”jquery.print.js”></script> <script src=”screw.builder.js”></script> <script src=”screw.matchers.js”></script> <script src=”screw.events.js”></script> <script src=”screw.behaviors.js”></script> <script src=”smoke.core.js”></script> <script src=”smoke.mock.js”></script> <script src=”smoke.stub.js”></script> <script src=”smoke.ajax.js”></script> <link rel=”stylesheet” href=”screw.css”> <script type=”text/javascript”> Screw.Unit(function() { describe(“Faz um requisicao Ajax e retorna um html como sucesso”, function() { it(“mockando uma requisicao ajax com sucesso”, function() { Smoke.Ajax.mock(“/meuRecurso”, “<p>Parabens!</p>”, 200); var resultado, httpStatus; $.ajax({ url:”/meuRecurso”, success: function(data, textStatus){ resultado = data; httpStatus = textStatus; }, error: function(error){ resultado = error.responseText; httpStatus = error.status; } Imposto.calcula = function(valor, imposto, taxa) { return ((valor * imposto) + taxa).toFixed(2); } }); Testes de unidade com DOM, Ajax e Callbacks utilizando Screw.Unit e Smoke Bibliotecas como jQuery nos ajudam a manipular a árvore DOM e principalmente a fazer requisições Ajax, que é a rotina mais comum para a grande maioria dos desenvolvedores, mas mesmo sendo muito corriqueira poucos testam e tomam o cuidado necessário. Como testaríamos nossa requisição Ajax? Como verificamos se o elemento HTML foi alterado? Fazendo uma requisição de verdade? Para testes com Ajax temos a opção de criar objetos falsos, mais conhecidos como mocks, que simulem a requisição e que façam a verificação se a mesma foi feita com sucesso, simulando seu método de callback de sucesso ou simulando uma requisição e um possível erro no server side execute o seu método de callback de erro. Para os exemplos de testes de Ajax usaremos o Screw.Unit como biblioteca de testes e Smoke como biblioteca de Mock. O Screw.Unit e o Smoke são duas bibliotecas ativas e existem inúmeros forks dos projetos presentes no Github. Utilizaremos como exemplo os fork de rsutphin e drogus, respectivamente. Nosso primeiro exemplo (Listagem 5) mostrará a criação de uma requisição Ajax, que simula o retorno de um código HTTP 200, para indicar sucesso e também o retorno de um HTML com uma mensagem de sucesso dentro de uma tag <p>. 48 wait(function(){ expect(resultado).to(equal, “<p>Parabens!</p>”); expect(httpStatus).to(equal, “success”); }, 50); }); }); </script> </head> <body></body> </html> Um teste simples para verificar se a resposta que o server side nos enviou está correta quando o status da requisição é sucesso. Neste primeiro exemplo, testamos o caso de sucesso da requisição, mas e quando essa requisição não é feita com sucesso, o que deve acontecer? Em nosso exemplo deve ser retornado um status 500 do HTTP e também um HTML dentro de uma tag <p> como alguma mensagem de erro. Vamos simular este comportamento e testar se o retorno está correto. Basta adicionarmos mais um comportamento de teste, através da função it(). Esta nova verificação será feita na Listagem 6. Listagem 6. Testando o caso de erro. it(“mockando uma requisicao ajax com erro”, function() { var resultado, httpStatus; Smoke.Ajax.mock(“/produto”, “<p>Oops</p>”, 500); $.ajax( { url : “/produto”, success : function(data, textStatus) { resultado = data; httpStatus = textStatus; }, error : function(error) { resultado = error.responseText; httpStatus = error.status; } }); wait(function() { expect(resultado).to(equal, “<p>Oops</p>”); expect(httpStatus).to(equal, 500); }, 50); }); No exemplo acima, definimos dentro do método wait, que ele deve retornar na variável resultado um valor igual a "<p>Oops</ p>" e dentro da variável httpStatus, um código igual a 500. Apenas simulamos o comportamento da requisição Ajax usando um mock. O exemplo que utilizamos foi apenas para demonstrar a possibilidade que o Smoke junto ao Screw.Unit nos oferece para usar um mock de uma requisição Ajax. Utilizando uma lógica de modificação do DOM como ficaria nosso teste? Listagem 7. Testando com Ajax alteração de um elemento na árvore DOM. <script type=”text/javascript”> function callback(data) { $(“#contentAjax”).html(data); } Screw.Unit(function() { describe(“Faz um requisicao Ajax e retorna um html como sucesso”, function() { it(“mockando e preenchendo div com conteudo do ajax”, function() { var html = “<span>Camiseta Vermelha</span>” expect($(“#contentAjax”).html()).to(equal, “”); Smoke.Ajax.mock(“/produto/camiseta”, html, 200); $.ajax( { url : “/produto/camiseta”, success : callback }); wait(function() { expect($(“#contentAjax”).html()) .to(equal, html); }, 50); }); }); }); </script> Na Listagem 7 simulamos uma requisição que quando efetuada com sucesso deve nos retornar um HTML que vai ser tratado por uma função criada por nos chamada callback que recebe como argumento data que nada mais é que a resposta da requisição em texto. No exemplo acima verificamos se uma parte da árvore DOM dos nossos componentes foram alterados de acordo com o que esperávamos. Ainda utilizando o exemplo da Listagem 7 podemos testar de outra maneira a alteração na árvore DOM usando apenas a função callback sem precisar simular a requisição Ajax, como é mostrado na Listagem 8. Listagem 8. Testando callback há alteração de um elemento na árvore DOM. <script type=”text/javascript”> Screw.Unit(function() { describe(“Preenche na div um html”, function() { it(“testando callback de um ajax”, function() { $(“#contentAjax”).html(“”); var html = “<span>Camiseta Amarela</span>”; callback(html); expect($(“#contentAjax”).html()).to(equal, html); }); }); }); </script> Verificar que um elemento foi alterado e que seu Ajax está seguindo o fluxo dos métodos de callback de sucesso e de erro é importante. Conseguimos testar nosso código Javascript, independentemente de chamadas ao server side. Focamos apenas no que realmente interessa, no client side da aplicação, no que é feito quando obtemos o resultado. Considerações finais Como foi visto, muitas vezes não damos a devida importância aos códigos que fazemos no client side da aplicação. Valorizamos na maioria das vezes o lado server side e podemos terminar com dados inconsistentes em nosso HTML ou mesmo efetuarmos cálculos inválidos que podem afetar também os resultados no servidor. Testes de unidade e abordagens como Test-Driven-Development são fáceis de serem aplicadas utilizando as ferramentas QUnit, ScrewUnit e Smoke• Referências • Screw Unit http://github.com/rsutphin/screw-unit/ • Smoke http://github.com/drogus/smoke/ • QUnit http://docs.jquery.com/QUnit • Blog da Caelum http://blog.caelum.com.br/2010/07/15/arredondamento-no-java-do-double-aobigdecimal • Blog do Luca Grulla http://www.lucagrulla.it/blog/2010/06/15/javascript-testing/ • How to test your JavaScript code with QUnit http://net.tutsplus.com/tutorials/javascript-ajax/how-to-test-your-javascript-codewith-qunit/ • Fault-tolerant system http://en.wikipedia.org/wiki/Fault-tolerant_system • Unobtrusive JavaScript http://en.wikipedia.org/wiki/Unobtrusive_JavaScript 49