Um exemplo de código de Javascript quebrado
Um dos maiores desafios enfrentados pelos aplicativos da Web é o suporte a navegadores da Web com diferentes versões. O código JavaScript que executa no Safari não funciona necessariamente no Windows® Internet Explorer (IE), Firefox ou Google Chrome. Esse desafio é uma herança da falta de testes do código Javascript na camada de apresentação desde o primeiro dia. Sem fazer teste de unidade do código, as organizações podem pagar por testes repetidos de aplicativos da Web ao decidir atualizar ou suportar novos navegadores. Este artigo mostra como usar um teste de unidade eficiente do código JavaScript para reduzir os custos de teste.
Um dos casos de uso comum é ter uma validação JavaScript do formulário de login. Considere o formulário na Listagem 1.
Listagem 1. O formulário de login
<FORM> <table> <tr> <td>Username</td> <td><input type="text" id="username"/></td> <td><span id="usernameMessage"></span></td> </tr> <tr> <td>Password</td> <td><input type="password" id="password"/></td> <td><span id="passwordMessage"></span></td> </tr> <tr> <td><input type="button" onclick="new appnamespace. ApplicationUtil().validateLoginForm()" value="Submit"/></td> </tr> </table> </FORM> |
O formulário é simples. Ele consiste em campos de nome de usuário e de senha. Ao clicar no botão submit, uma validação específica de formulário é realizada por meio de
ApplicationUtil. Esse é um objeto JavaScript responsável por validar o formulário HTML. A listagem 2 mostra o código do objeto ApplicationUtil.
Listagem 2. O código quebrado do objeto ApplicationUtil
appnamespace = {};
appnamespace.ApplicationUtil = function() {};
appnamespace.ApplicationUtil.prototype.validateLoginForm = function(){
var error = true;
document.getElementById("usernameMessage").innerText = "";
document.getElementById("passwordMessage").innerText = "";
if (!document.getElementById("username").value) {
document.getElementById("usernameMessage").innerText =
"This field is required";
error = false;
}
if (!document.getElementById("password").value) {
document.getElementById("passwordMessage").innerText =
"This field is required";
error = false;
}
return error;
};
|
Na Listagem 2, o objeto ApplicationUtil fornece uma validação simples. Ele verifica se os campos de nome de usuário e senha foram preenchidos. Se um campo estiver vazio, uma mensagem de erro dizendo This field is required aparece.
Embora o código anterior funcione no Internet Explorer 8 e Safari 5.1, ele não funciona no Firefox 3.6, pois a propriedade innerText não é suportada pelo Firefox. Geralmente, o maior problema (no código anterior e em código JavaScript semelhante) é que não é fácil detectar se o código JavaScript escrito é compatível com diferentes navegadores.
Uma solução para esse problema é ter um teste de unidade automatizado que verifica se o código está correto em vários navegadores.
Uma das melhores estruturas de teste de unidade JavaScript é a biblioteca JsTestDriver, que fornece teste para código JavaScript em vários navegadores. A Figura 1 mostra a arquitetura do JsTestDriver.
Figura 1. Arquitetura do JsTestDriver
O servidor é responsável por carregar o código executador dos casos de teste de JavaScript nos diferentes navegadores após serem capturados. O navegador pode ser capturado pela linha de comando ou apontando o navegador para a URL do servidor. Quando o navegador é capturado, ele é chamado de navegador escravo. O servidor carrega o código JavaScript, executa as etapas de teste em cada navegador e retorna os resultados ao cliente.
O cliente (linha de comando) precisa de dois itens principais:
- Arquivos JavaScript — arquivos de origem e arquivos de teste, e
- Arquivo de configuração — para organizar o carregamento de arquivos de origem e dos arquivos de teste.
Essa arquitetura é flexível, permitindo que um único servidor capture qualquer número de navegadores de outras máquinas na rede. Por exemplo, pode ser útil se o seu código estiver sendo executado no Linux, mas você deseja executar as etapas de teste no Microsoft Internet Explorer em outra máquina Windows.
Para começar a trabalhar com a biblioteca JsTestDriver, faça o download da versão mais recente do JsTestDriver 1.3.2.
Escrever o código do teste de unidade
Agora, vamos escrever as etapas de teste JavaScript. Visando a simplicidade, eu irei testar os seguintes casos:
- Um valor vazio para nome de usuário e um valor vazio para senha.
- Um valor vazio para nome de usuário e um valor não vazio para senha.
- Um valor não vazio para nome de usuário e um valor vazio para senha.
A Listagem 3 mostra parte do código do objeto ApplicationUtilTest que representa o objeto TestCase.
Listagem 3. Parte do código do objeto ApplicationUtilTest
ApplicationUtilTest = TestCase("ApplicationUtilTest");
ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};
ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!", "This field is required",
document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!", "This field is required",
document.getElementById("passwordMessage").innerHTML);
};
|
O objeto ApplicationUtilTest é criado por meio do objeto
TestCase do JsTestDriver. Se você estiver familiarizado com a estrutura JUnit, também conhecerá os métodos setUp e testXXX. O método setUp é usado para inicializar as etapas de teste. Neste exemplo, eu o usei para declarar o fragmento HTML que será usado pelos demais métodos de etapas de teste.
A anotação DOC é uma convenção JsTestDriver usada facilmente para declarar um fragmento HTML.
No método testValidateLoginFormBothEmpty, um objeto
ApplicationUtil é criado para uso dentro do método de etapas de teste. O código em seguida simula a inserção de um nome de usuário e senha vazios, recuperando seus elementos DOM e configurando seus valores para valores vazios. O método validateLoginForm
é chamado para realizar a real validação do formulário. Finalmente, assertEquals é solicitado para garantir que a mensagem nos elementos de período usernameMessage e passwordMessage estejam corretos, ou seja: This field is required.
No JsTestDriver, é possível usar estas construções:
-
fail("msg")- indica que o teste deve falhar e que o parâmetro de mensagem será exibido como uma mensagem de erro. -
assertTrue("msg", actual)— declara que o parâmetro real é verdadeiro. Se não, o parâmetro de mensagem será exibido como uma mensagem de erro. -
assertFalse("msg", actual)— declara que o parâmetro real é falso. Se não, o parâmetro de mensagem será exibido como uma mensagem de erro. -
assertSame("msg", expected, actual)— declara que o parâmetro real é o mesmo que o parâmetro esperado. Se não, o parâmetro de mensagem será exibido como uma mensagem de erro. -
assertNotSame("msg", expected, actual)— declara que o parâmetro real não é o mesmo que o parâmetro esperado. Se não, o parâmetro de mensagem será exibido como uma mensagem de erro. -
assertNull("msg", actual)— declara que o parâmetro real é nulo. Se não, o parâmetro de mensagem será exibido como uma mensagem de erro. -
assertNotNull("msg", actual)— declara que o parâmetro real não é nulo. Se não, o parâmetro de mensagem será exibido como uma mensagem de erro.
O código dos outros métodos inclui as outras etapas de teste. A Listagem 4 mostra o código completo do objeto de etapas de teste.
Listagem 4. O código completo do objeto ApplicationUtil
ApplicationUtilTest = TestCase("ApplicationUtilTest");
ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};
ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!", "This field is required",
document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!", "This field is required",
document.getElementById("passwordMessage").innerHTML);
};
ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
/* Simulate empty user name and password */
document.getElementById("username").value = "";
document.getElementById("password").value = "anyPassword";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!",
"This field is required", document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!",
"", document.getElementById("passwordMessage").innerHTML);
};
ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyPassword = function () {
var applicationUtil = new appnamespace.ApplicationUtil();
document.getElementById("username").value = "anyUserName";
document.getElementById("password").value = "";
applicationUtil.validateLoginForm();
assertEquals("Username is not validated correctly!",
"", document.getElementById("usernameMessage").innerHTML);
assertEquals("Password is not validated correctly!",
"This field is required", document.getElementById("passwordMessage").
innerHTML);
};
|
Configurar os diferentes navegadores com os testes
Uma das práticas recomendadas ao testar o código JavaScript é colocar o código de origem JavaScript em uma pasta diferente do código de teste. Para o exemplo mostrado na Figura 2, eu nomeei a pasta de origem JavaScript de "js-src", e a pasta de teste JavaScript de "js-test", na pasta-pai "js".
Figura 2. A estrutura de pasta de teste JavaScript
Após estruturar as pastas de origem e de teste, é preciso fornecer o arquivo de configuração. Por padrão, o executador JsTestDriver procura um arquivo de configuração chamado jsTestDriver.conf. O nome do arquivo de configuração pode ser alterado na linha de comando. A Listagem 5 mostra o conteúdo do arquivo de configuração JsTestDriver.
Listagem 5. O conteúdo do arquivo de configuração JsTestDriver
server: http://localhost:9876 load: - js-src/*.js - js-test/*.js |
O arquivo de configuração está escrito no formato YAML. A diretiva server especifica o endereço do servidor de teste, enquanto a diretiva load indica quais arquivos JavaScript deverão ser carregados nos navegadores, e em qual ordem.
Agora, vamos executar a classe de etapas de teste nos navegadores IE, Firefox e Safari.
Para executar as classes de etapas de teste, é preciso iniciar o servidor. O servidor
JsTestDriver pode ser iniciado usando a seguinte linha de comando:
java -jar JsTestDriver-1.3.2.jar --port 9876 --browser "[Firefox Path]",
"[IE Path]","[Safari Path]"
|
Ao usar essa linha de comando, o servidor será iniciado na Porta 9876 e irá capturar os navegadores Firefox, IE e Safari na máquina.
Após iniciar e capturar os navegadores, use a seguinte linha de comando para executar as classes de etapas de teste:
java -jar JsTestDriver-1.3.2.jar --tests all |
Após executar o comando, você verá os resultados da primeira execução, como mostra a Listagem 6.
Listagem 6. Os resultados da primeira execução
Total 9 tests (Passed: 6; Fails: 3; Errors: 0) (16.00 ms)
Firefox 3.6.18 Windows: Run 3 tests (Passed: 0; Fails: 3; Errors 0) (8.00 ms)
ApplicationUtilTest.testValidateLoginFormBothEmpty failed (3.00 ms):
AssertError: Username is not validated correctly! expected "This field
is required" but was "" Error("Username is not validated correctly!
expected \"This field is required\" but was \"\"")@:0()@http://localhost
:9876/test/js-test/TestApplicationUtil.js:16
ApplicationUtilTest.testValidateLoginFormWithEmptyUserName failed (3.00 ms):
AssertError: Username is not validated correctly! expected "This field is
required" but was "" Error("Username is not validated correctly! expected
\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test
/js-test/TestApplicationUtil.js:29
ApplicationUtilTest.testValidateLoginFormWithEmptyPassword failed (2.00 ms):
AssertError: Password is not validated correctly! expected "This field is
required" but was "" Error("Password is not validated correctly! expected
\"This field is required\" but was \"\"")@:0()@http://localhost:9876/test/
js-test/TestApplicationUtil.js:42
Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (2.00 ms)
Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0;
Errors 0) (16.00 ms)
Tests failed: Tests failed. See log for details.
|
Observe na Listagem 6 que o principal problema é com o Firefox. Os testes são executados com sucesso no Internet Explorer e no Safari.
Corrigir o código JavaScript e executar novamente as etapas de teste
Vamos corrigir o código JavaScript quebrado. Usaremos
innerHTML ao invés de
innerText. A Listagem 7
mostra o código corrigido do objeto ApplicationUtil.
Listagem 7. O código corrigido do objeto ApplicationUtil
appnamespace = {};
appnamespace.ApplicationUtil = function() {};
appnamespace.ApplicationUtil.prototype.validateLoginForm = function(){
var error = true;
document.getElementById("usernameMessage").innerHTML = "";
document.getElementById("passwordMessage").innerHTML = "";
if (!document.getElementById("username").value) {
document.getElementById("usernameMessage").innerHTML =
"This field is required";
error = false;
}
if (!document.getElementById("password").value) {
document.getElementById("passwordMessage").innerHTML =
"This field is required";
error = false;
}
return error;
};
|
Execute novamente o objeto de etapas de teste usando o argumento de linha de comando --test all. A Listagem 8 mostra os resultados da segunda execução.
Listagem 8. Os resultados da segunda execução
Total 9 tests (Passed: 9; Fails: 0; Errors: 0) (9.00 ms) Firefox 3.6.18 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (9.00 ms) Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms) Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (0.00 ms) |
Como mostra a Listagem 8, o código JavaScript agora está funcionando direito no IE, Firefox e Safari.
Neste artigo, você aprendeu a testar o código de um aplicativo JavaScript em diferentes navegadores usando uma das mais poderosas ferramentas de teste de unidade do JavaScript (JsTestDriver). Você sabe o que é o JsTestDriver, como configurá-lo e como usá-lo dentro de um aplicativo da Web para garantir a qualidade e robustez do código JavaScript do seu aplicativo.
| Descrição | Nome | Tamanho | Método de download |
|---|---|---|---|
| The source code | simple.zip | 3.35MB | HTTP |
Informações sobre métodos de download
Aprender
- Confira JUnit.org para mais informações sobre o uso da estrutura de teste JUnit.
- Saiba mais sobre YAML, um padrão de serialização de dados de fácil uso para todas as linguagens de programação.
- Encontre extensas instruções, ferramentas e atualizações de projeto para ajudar a desenvolver com tecnologias de software livre e usá-las com produtos IBM.
- Mantenha-se atualizado participando de eventos técnicos e webcasts do developerWorks.
- Siga o developerWorks no Twitter.
- Escute os Podcasts do developerWorks para acompanhar entrevistas interessantes e discussões sobre desenvolvimento de software.
Obter produtos e tecnologias
- Como você está interessado em teste de aplicativos, experimente nosso software de avaliação do IBM Rational Functional Tester.
- Acesse a
página de download do JsTestDriver para obter o código e plug-ins mais recentes, e mais.
- Confira outra ferramenta para teste de unidade, o Dojo Objective Harness (DOH), no Web site do projeto dojo.
- Explore a diversidade de
estruturas de teste de unidade JavaScript.
Discutir
- Conecte-se a outros usuários do developerWorks por meio da comunidade do developerWorks enquanto explora os blogs, fóruns, grupos e wikis voltados para desenvolvedores.
- Ajude a desenvolver o software livre do mundo real na comunidade do developerWorks.

Hazem Saleh tem seis anos de experiência em tecnologias JEE e de código aberto. Ele é comprometido com o Apache MyFaces e o iniciador de muitos componentes nos projetos do MyFaces, como o Tomahawk CAPTCHA, Commons ExportActionListener, Media, PasswordStrength e outros. Ele é o fundador do GMaps4JSF (um projeto de integração do Google Maps com o JavaServer Faces) e Mashups4JSF (um projeto de integração de serviços de mashup com o JavaServer Faces). Ele é o autor do livro "The Definitive Guide to Apache MyFaces and Facelets (Apress)" e muitos artigos sobre JSF, um autor contribuidor do developerworks e um palestrante público sobre JSF. Ele agora trabalha para a IBM Egito como engenheiro contratado. Hazem é reconhecido como Especialista no assunto em tecnologias web 2.0.