Universidade Estadual de Maringá Centro de Tecnologia – Departamento de Informática Especialização em Desenvolvimento de Sistemas para Web
Buster.js, um framework de testes para JavaScript
Obedi de Paula Ferreira
Prof. Me. Gécen de Marchi
Orientador
Maringá, 2013
Universidade Estadual de Maringá Centro de Tecnologia – Departamento de Informática Especialização em Desenvolvimento de Sistemas para Web
Obedi de Paula Ferreira
Buster.js, um framework de testes para JavaScript
Trabalho submetido à Universidade Estadual de Maringá como requisito para obtenção do título de Especialista em
Desenvolvimento de Sistemas para Web.
Orientador: Prof. Me. Gécen de Marchi
Maringá, 2013
Universidade Estadual de Maringá Centro de Tecnologia – Departamento de Informática Especialização em Desenvolvimento de Sistemas para Web
Obedi de Paula Ferreira
Buster.js, um framework de testes para JavaScript
Maringá, 12 de Abril de 2013.
Prof. Flávio Uber Ass.:________________________
Prof. Munif Gebara Junior Ass.:________________________
Prof. Dr. Gécen de Marchi (Orientador) Ass.:________________________
RESUMO
Um dos principais desafios da Engenharia de Software é lidar com o aumento da complexidade do software e a diminuição dos prazos de entrega, dado que esta relação compromete diretamente a qualidade do software. No intuito de minimizar o impacto deste e de outros desafios, foram criados ao longo dos anos diferentes padrões e metodologias de desenvolvimento que pudessem proporcionar ganho de produtividade e garantir maior qualidade do software. Dentre essas metodologias, um conjunto conhecido como “metodologias ágeis” tem se destacado nos últimos anos. Uma destas metodologias ágeis, o Extreme Programming, ou XP, introduziu uma prática popularmente conhecida como Test-Driven Development, ou TDD, que consiste em implementar testes automatizados que irão guiar o desenvolvimento de cada funcionalidade do software. Escrever testes automatizados para JavaScript não é uma tarefa tão simples quanto nas demais linguagens de programação modernas, basicamente pelo fato de que existem uma série de fatores externos à linguagem que em alguns casos dificultam com que código JavaScript seja testado de forma apropriada. Dentre estes fatores está o fato de JavaScript depender diretamente do HTML, do CSS, e de como cada navegador renderiza estes elementos. Apesar de existirem diversos frameworks de testes para JavaScript disponíveis no mercado, a maioria deles ainda não alcançou o nível de maturidade necessário para se difundir como referência nesse domínio, assim como por exemplo, o JUnit é referência para testes automatizados para a linguagem Java. Sendo assim, após entender as peculiaridades pertinentes aos ambientes onde esse código é executado (os navegadores) e conhecer o estado atual dos frameworks de testes automatizados para JavaScript, encontrou-se o Buster.JS, um framework que apesar de encontrar-se em fase beta, se mostrou bastante robusto e completo, com funcionalidades que ajudam o desenvolvedor a focar seus esforços mais na qualidade dos testes e não na infraestrutura dos testes para diferentes navegadores. O Buster.JS possui instalação e configuração simples, e linguagem similar a frameworks de outras linguagens populares do mercado. Ele utiliza uma técnica baseada no conceito mestre x escravo para permitir que os testes sejam executados ao mesmo tempo em diversos navegadores diferentes, nas versões e plataformas escolhidas pelo desenvolvedor.
Palavras-chave: JavaScript, Testes Unitários, Desenvolvimento Guiado por Testes, Testes Automatizados, Buster.JS.
ABSTRACT
One of the main challenges of Software Engineering in is how to handle the growth of software complexity and the deadline shortenings, given that such relation jeopardizes directly the quality of the software. In an attempt of minimizing the impact of these and other challenges, a variety of patterns and software development methodologies were created throughout the years, that could help improve productivity and assure better quality of the software. Among these methodologies, the so called “agile methodologies” stood out in recent years. Among these methodologies, the Extreme Programming introduced a practice popularly known as TDD (Test-Driven Development), which consists in implementing automated tests that will guide the development of each functionality of the software. Writing automated tests for JavaScript is not a simple task as in other modern programming languages, primarily because there are a lot of factors external to language that in some cases make testing JavaScript code properly more difficult. Among these factors is the fact that JavaScript depends heavily on HTML, CSS, and how each browser renders these elements. Although there are many JavaScript frameworks available, most of them have not yet reached the necessary level of maturity to stand as an absolute reference in this domain, just like JUnit is a reference to automated tests for the Java programming language, for example. Therefore, after understanding the peculiarities relevant to the environments where this code is executed (the browsers) and after knowing the state of art of JavaScript testing frameworks, Buster.JS was highlighted. Buster.JS is tool that despite being beta, was found to be robust and complete, with features that can help developers to focus on the tests quality over the tests infrastructure for different browsers. Buster.JS installation and configuration is simple, and its language is quite similar to other popular frameworks from other languages. It uses a master x slave technique to allow tests to be executed simultaneously in different browsers, in the versions and platforms chosen by the developer.
Keywords: JavaScript, Unit Tests, Test-Driven Development, Automated Tests, Buster.JS.
LISTA DE ILUSTRAÇÕES
Figura 1 - Exemplo de documento HTML ............................................................................... 18
Figura 2 - Árvore DOM da figura anterior ............................................................................... 19
Figura 3 - Exemplo de um “assert equals” no framework JUnit, para Java ............................. 24
Figura 4 - Exemplo de caso de teste em Buster.js .................................................................... 28
Figura 5 – Calculadora.js: Classe Calculadora, em JavaScript ................................................ 29
Figure 6 – Calculadora-test.js: Teste da class Calculadora, em JavaScript .............................. 29
Figure 7 - Exemplo de estrutura de um projeto ........................................................................ 30
Figura 8 – Arquivo de configuração do Buster.js ..................................................................... 30
Figura 9 - Organização das classes e testes .............................................................................. 31
Figura 10 - Arquivo buster.html ............................................................................................... 32
Figura 11 - Relatório de execução dos testes ............................................................................ 32
Figura 12 - Resultado de execução com falha .......................................................................... 33
Figure 13 - Comando buster static ............................................................................................ 34
Figura 14 - Estrutura do exemplo validação de formulário ...................................................... 35
Figura 15 - Conteúdo do arquivo test/fixtures/formulario.html ............................................... 35
Figura 16 - Configuração do Buster.js para uso de fixtures ..................................................... 36
Figura 17 - Conteúdo da classe ValidaForm.js ......................................................................... 37
Figura 18 - Formulário de login do arquivo test/fixtures/formulario.html ............................... 37
Figura 19 - Formulário de login apresentando erro .................................................................. 38
Figura 20 - Clase de teste ValidaForm-test.js ........................................................................... 38
Figura 21 - Comando buster server .......................................................................................... 39
Figura 22 - Página de captura de slaves ................................................................................... 39
Figura 23 - Relatório da execução dos testes ........................................................................... 40
Figura 24 - Tentativa de execução dos testes sem slaves capturados ....................................... 40
Figura 25 - Relatório de execução com falhas ......................................................................... 40
Figura 26 - Configuração do Buster.js no modo node .............................................................. 41
Figura 27 - Classe Calculadora.js alterada para o modo node.................................................. 42
Figura 28 Classe Calculadora-test.js alterada para o modo node ............................................. 42
SUMÁRIO
CAPÍTULO 1. INTRODUÇÃO ................................................................................................................... 8
1.1. PROBLEMA ...................................................................................................................................................... 9
1.2. JUSTIFICATIVA ............................................................................................................................................ 10
1.3. MOTIVAÇÃO ................................................................................................................................................. 10
1.4. OBJETIVO GERAL ....................................................................................................................................... 10
1.5. OBJETIVOS ESCPECÍFICOS ..................................................................................................................... 11
1.6. LIMITAÇÕES DA PESQUISA .................................................................................................................... 11
1.7. ESTRUTURA DO TRABALHO ................................................................................................................. 11
CAPÍTULO 2. REVISÃO BIBLIOGRÁFICA .......................................................................................... 13
2.1. DESENVOLVIMENTO ÁGIL ..................................................................................................................... 13
2.1.1. O MANIFESTO ÁGIL ................................................................................................................................. 13
2.2. EXTREME PROGRAMMING (XP) .......................................................................................................... 14
2.3. TEST-DRIVEN DEVELOPMENT ............................................................................................................. 16
2.3.1. CICLO DE DESENVOLVIMENTO COM TDD ............................................................................................ 17
2.4. A LINGUAGEM JAVASCRIPT .................................................................................................................... 17
2.4.1. ECMASCRIPT, O NÚCLEO DA LINGUAGEM JAVASCRIPT .................................................................... 17
2.4.2. DOM ........................................................................................................................................................... 18
2.4.3. BOM ........................................................................................................................................................... 19
2.4.4. CARACTERÍSTICAS DA LINGUAGEM ....................................................................................................... 19
2.4.5. TESTES UNITÁRIOS EM JAVASCRIPT ..................................................................................................... 20
CAPÍTULO 3. FRAMEWORKS DE TESTES PARA JAVASCRIPT .................................................... 22
3.1. XUNIT TEST FRAMEWORKS .................................................................................................................. 22
3.1.1. BDD ............................................................................................................................................................ 23
3.2. ESTRUTURA BÁSICA DE UM FRAMEWORK DE TESTES ............................................................. 23
3.2.1. CASO DE TESTE, OU CENÁRIO DE TESTE .............................................................................................. 23
3.2.1.1. ASSERÇÕES ................................................................................................................................................. 24
3.2.2. FIXTURES ................................................................................................................................................... 24
3.2.3. SUÍTE DE TESTES ...................................................................................................................................... 25
3.3. TEST RUNNERS ........................................................................................................................................... 25
3.3.1. RUNNERS “IN-BROWSER” ...................................................................................................................... 26
3.3.2. RUNNERS HEADLESS ............................................................................................................................... 26
CAPÍTULO 4. BUSTER.JS ........................................................................................................................ 28
4.1. INSTALAÇÃO E CONFIGURAÇÃO .......................................................................................................... 29
4.2. MODO IN-BROWSER ................................................................................................................................. 31
4.2.1. MODO HTML ........................................................................................................................................... 31
4.2.2. MODO ESTÁTICO ....................................................................................................................................... 33
4.2.3. MODO SERVIDOR ...................................................................................................................................... 34
4.3. MODO HEADLESS ...................................................................................................................................... 41
4.4. MODO NODE ................................................................................................................................................ 41
4.5. ASSERÇÕES ................................................................................................................................................... 42
4.5.1. ASSERT.SAME() ......................................................................................................................................... 43
4.5.2. ASSERT.EQUALS() ..................................................................................................................................... 43
4.5.3. ASSERT.GREATER() .................................................................................................................................. 43
4.5.4. ASSERT.LESS() ........................................................................................................................................... 43
4.5.5. ASSERT.DEFINED() ................................................................................................................................... 43
4.5.6. ASSERT.ISNULL() ...................................................................................................................................... 43
4.5.7. ASSERT.ISOBJECT() .................................................................................................................................. 43
4.5.8. ASSERT.ISFUNCTION() ............................................................................................................................. 44
4.5.9. ASSERT.ISTRUE() ...................................................................................................................................... 44
4.5.10. ASSERT.ISFALSE() ..................................................................................................................................... 44
4.5.11. ASSERT.ISSTRING() .................................................................................................................................. 44
4.5.12. ASSERT.ISBOOLEAN() .............................................................................................................................. 44
4.5.13. ASSERT.ISNUMBER() ............................................................................................................................... 44
4.5.14. ASSERT.ISNAN() ....................................................................................................................................... 45
4.5.15. ASSERT.ISARRAY() ................................................................................................................................... 45
4.5.16. ASSERT.EXCEPTION() ............................................................................................................................... 45
4.5.17. ASSERT.CONTAINS() ................................................................................................................................. 45
4.5.18. ASSERT.TAGNAME() ................................................................................................................................. 45
4.5.19. ASSERT.CLASSNAME() ............................................................................................................................. 45
CAPÍTULO 5. CONCLUSÃO .................................................................................................................... 47
REFERÊNCIAS .................................................................................................................................................. 48
8
CAPÍTULO 1. INTRODUÇÃO
Uma das questões mais complexas no que diz respeito ao processo de desenvolvimento de
software, é como garantir a qualidade do software (FEITOSA, 2007)
Dos fatores que afetam a qualidade do software, alguns podem ser medidos diretamente,
como por exemplo a relação quantidade de falhas por linhas de código, enquanto que outros
apenas podem ser medidos indiretamente, como a usabilidade, por exemplo. São estes fatores
que calculados, servem de indicadores da qualidade do software (PRESSMAN, 2005, apud
FEITOSA, 2007).
SOMMERVILLE (2003, apud FEITOSA, 2007) afirma que um dos principais desafios da
Engenharia de Software neste século é lidar com o aumento da complexidade do software e a
diminuição dos prazos de entrega, dado que esta relação compromete diretamente a qualidade
do software.
No intuito de minimizar o impacto deste e de outros desafios, foram criados ao longo dos
anos diferentes padrões e metodologias de desenvolvimento de software que pudessem
proporcionar ganho de produtividade e garantir maior qualidade do software.
Dentre essas metodologias, um conjunto conhecido como “metodologias ágeis” vem se
mostrando úteis na busca de software de melhor qualidade, produzidos em menos tempo e de
uma forma mais econômica que o habitual. Em geral essas metodologias são regidas por um
conciso conjunto de valores, princípios e práticas, que diferem substancialmente da forma
tradicional de se desenvolver software (TELES, 2008).
No desenvolvimento de software, CIFANI (2011) cita como outro grande problema a
manutenção do software em produção, pois a necessidade de mudança no software pode surgir
a todo instante e uma pequena mudança em uma pequena parte do software pode ter impacto
nas demais partes do mesmo. Torna-se então necessário uma forma de desenvolvimento que
possa de alguma forma dar ao desenvolvedor a visibilidade do impacto de uma mudança no
software.
9
O Desenvolvimento Guiado por Testes, ou TDD (do ingles, Test-Driven Development), é uma
das práticas do XP que tem por objetivo antecipar a identificação e correção de falhas durante
o desenvolvimento (TELES, 2008).
Esta prática consiste em implementar testes automatizados para cada funcionalidade do
software antes mesmo de implementá-la (FEITOSA, 2007). Dessa forma, após a
implementação da funcionalidade propriamente dita, o desenvolvedor pode executar os testes
automatizados e verificar se o código implementado está correto, pois se o teste passar, o
desenvolvedor saberá que o código foi bem implementado (FEITOSA, 2007).
De forma semelhante, após uma mudança em qualquer parte do software, o desenvolvedor
saberá o impacto daquela mudança nas demais partes do sistema, verificando quantos testes
não passaram.
1.1. PROBLEMA
Atualmente as linguagens de desenvolvimento mais modernas e populares, como o Ruby, o
Java, e muitas outras, possuem uma enorme gama de frameworks diversas ferramentas que
dão suporte ao uso de testes automatizados.
No entanto, apesar de a linguagem JavaScript estar massivamente presente no
desenvolvimento web, existem muitos elementos a serem considerados e que dificultam os
testes de JavaScript de forma apropriada. Um destes elementos é a interdependência entre
HTML e CSS.
Pode-se dizer que o problema em se testar JavaScript se resume aos navegadores. Existem
diversos navegadores disponíveis, e cada um com suas peculiaridades. Linguagens de
desenvolvimento tradicionalmente excelentes para testes unitários, geralmente tem seus testes
rodando em um ambiente padronizado, previsível e estável, onde os efeitos de certas ações
são facilmente compreensíveis (ZAKAS, 2009).
Atualmente existem disponíveis diversos frameworks que dão suporte a testes automatizados
em JavaScript, porém a maioria delas ainda não alcançou o nível de maturidade necessário
para se difundir como referência nesse domínio, assim como por exemplo, o JUnit é
referência para testes automatizados para a linguagem Java.
10
1.2. JUSTIFICATIVA
NIST (2002, apud TELES, 2008) estima que falhas de software causem um prejuízo anual
aproximado de mais de 60 bilhões de dólares para a economia americana. Um terço destes
custos poderia ser eliminado se fossem utilizadas infraestruturas de testes mais adequadas,
que permitissem identificar e resolver falhas mais cedo e de forma mais eficaz.
Apesar desta realidade, grande parte dos desenvolvedores de software hoje, utilizam-se da
premissa de que as bibliotecas JavaScript por eles usadas já foram previamente testadas por
seus mantenedores, e com isso ignoram a importância de se ter testes que garantam o
comportamento esperado dessas bibliotecas em suas aplicações.
Aliado a este fato, o desconhecimento de frameworks de testes maduros contribuem para tal
negligência em se testar código JavaScript.
Sendo assim, para se caminhar em direção a uma cobertura de testes satisfatória no código
JavaScript de uma aplicação, é importante entender as peculiaridades pertinentes aos
ambientes onde esse código é executado (os navegadores) e conhecer o estado atual dos
frameworks de testes automatizados para que se optar pelo mais indicado.
1.3. MOTIVAÇÃO
O uso de testes automatizados é um domínio no qual muito tem se falado ao longo dos anos.
A maioria das linguagens modernas possuem ferramentas robustas e maduras para suporte a
testes, e em alguns casos essas linguagens já até possuem métodos nativos para teste, como é
o caso da linguagem Ruby.
No entanto, pouca importância tem-se dado aos testes de código JavaScript, e pouco se
conhece sobre as ferramentas de teste existentes.
1.4. OBJETIVO GERAL
Propor o uso de um dos frameworks de testes unitários disponíveis para JavaScript, que dê
suporte robusto à construção de testes unitários, permitindo que desenvolvedores deem maior
11
atenção à construção de testes de qualidade, e não investir tempo demasiado na criação e
manutenção de infraestrutura necessária para a execução dos testes em diferentes ambientes.
1.5. OBJETIVOS ESCPECÍFICOS
Os objetivos específicos para se alcançar o objetivo geral traçado são:
i. Estudar testes de unidade para compreender seu papel dentro do domínio dos testes de
software;
ii. Identificar e estudar outros níveis de testes automatizados de software;
iii. Estudar frameworks de testes disponíveis para JavaScript;
iv. Escolher um dos frameworks estudados para um aprendizado mais aprofundado;
v. Implementar uma suíte de testes utilizando o framework escolhido para colocar em
prática os conceitos estudados.
1.6. LIMITAÇÕES DA PESQUISA
Este trabalho se limita a estudar os pontos fortes e fracos do framework a ser escolhido, sem a
preocupação de sugerir melhorias ou tentar encontrar formas de mitigar as limitações da
ferramenta.
Quanto a abordagem e profundidade dos testes, serão abordados apenas testes unitários de
software.
1.7. ESTRUTURA DO TRABALHO
Este trabalho é dividido em cinco capítulos, conforme descrito abaixo:
O Capítulo 2, Revisão Bibliográfica, apresenta a base teórica sobre Testes de Software,
Desenvolvimento Ágil e Desenvolvimento Guiado por Testes. São também apresentados os
fundamentos básicos da linguagem JavaScript.
O Capítulo 3, Metodologia de Desenvolvimento, relata as etapas da metodologia de pesquisa
utilizada.
12
No Capítulo 4, Frameworks de TDD para JavaScript, são apresentados alguns dos
frameworks mais populares do mercado atualmente e são levantados os aspectos desejáveis a
um framework ideal para este fim. Ao final é apresentada uma análise detalhada do framework
que melhor satisfaz estes critérios.
Finalizando o trabalho, o Capítulo 5, Conclusão, apresenta a conclusão geral do trabalho, bem
como dificuldades, contribuições e possíveis trabalhos futuros.
13
CAPÍTULO 2. REVISÃO BIBLIOGRÁFICA
2.1. DESENVOLVIMENTO ÁGIL
Ao longo dos anos, alguns desenvolvedores acreditaram que os modelos tradicionais de
desenvolvimento, como o modelo cascata, estavam errados, pois suas práticas burocráticas
não se encaixavam na realidade dinâmica do mercado de software (CIFANI, 2011). O maior
problema destas metodologias era o fato de existirem muitas etapas vitais e sequenciais, ao
ponto de que se uma etapa atrasasse, todo o projeto sofreria impacto direto deste atraso
(CIFANI, 2011).
É neste contexto que com o tempo algumas metodologias foram surgindo com o intuito de
minimizar estes problemas, priorizando interações mais constantes com os clientes e entregas
de subprodutos em períodos mais curtos de tempo.
Apesar de muitas dessas metodologias existirem há anos, o termo desenvolvimento ágil
ganhou força após a criação do Manifesto Ágil, um conjunto de princípios a serem seguidos
para se obter sucesso no desenvolvimento de software frente as necessidades do mundo atual.
2.1.1. O Manifesto Ágil
O Manifesto Ágil (BECK et al, 2001) foi criado em Fevereiro de 2001 e surgiu a partir de um
encontro de 17 profissionais veteranos no desenvolvimento de software que queriam discutir
formas de melhorar o desempenho de seus projetos de software.
Cada um dos envolvidos na criação do Manifesto Ágil possuía suas próprias práticas e teorias
particulares sobre a melhor forma se desenvolver software, porém havia um consenso sobre
um pequeno conjunto de 12 princípios que havia sido respeitado em todos os projetos de
sucesso (TELES, 2008). Estes princípios se resumem nos seguintes valores descritos no
manifesto (BECK et al, 2011):
i. Indivíduos e interações mais que processos e ferramentas
14
ii. Software em funcionamento mais que documentação abrangente
iii. Colaboração com o cliente mais que negociação de contratos
iv. Responder a mudanças mais que seguir um plano
“Ou seja, mesmo havendo valor nos itens à direita, valorizamos mais os itens à esquerda”
(BECK et al, 2011).
Sendo assim, o termo Desenvolvimento Ágil hoje descreve qualquer abordagem de
desenvolvimento que siga estes princípios (TELES, 2008).
2.2. EXTREME PROGRAMMING (XP)
O Extreme Programming, ou XP, como é comumente conhecida, é uma das metodologias de
desenvolvimento ágil mais populares atualmente. Sua origem vem de algumas práticas do
desenvolvimento em Smalltalk, disseminadas principalmente por Kent Beck e Ward
Cunningham em meados da década de 80. Dentre essas práticas, pode-se citar a programação
em par, refatoração (melhoria no código), feedback constante do cliente e testes
automatizados (TELES, 2005).
Em projetos XP, o software é desenvolvido em um modo interativo e incremental, onde a cada
semana os desenvolvedores se reúnem com o cliente para priorizar um conjunto fechado de
funcionalidades que possam ser implementadas completamente no ciclo de uma semana
(TELES, 2008).
Após esse período, o cliente tem a oportunidade de utilizar e avaliar o que foi produzido e
com base nessas experiências, sugerir melhorias e mudanças (TELES, 2008). Nesse ponto, o
desenvolvimento guiado por testes torna-se essencial para que os desenvolvedores possam
realizar mudanças com segurança, visto que os testes indicarão eventuais falhas decorrentes
da alteração. Os testes podem também ajudar a garantir que o código desenvolvido atenda as
necessidades levantadas pelo cliente (FEITOSA, 2007).
Quatro valores regem o XP. São eles:
i. Comunicação: XP mantém a comunicação fluente através do uso de práticas
que não podem ser feitas sem se comunicar, tais como testes unitários, programação
em par e estimativa de tarefas (BECK e ANDRES, 2004). O efeito dessas simples
15
práticas é que desenvolvedores e gerentes tem que se comunicar sobre o que realmente
é importante no projeto.
ii. Simplicidade: Segundo BECK e ANDRES (2004), XP parte do princípio de
que é melhor fazer algo simples no primeiro momento e caso necessário, investir
tempo para que este algo seja alterado, do que fazer algo mais complexo e que talvez
nunca venha a ser usado. Em outras palavras, os desenvolvedores devem codificar
primeiro tudo que é claramente importante para o software, evitando desenvolver o
que não é essencial (FEITOSA, 2007). Simplicidade e Comunicação possuem uma
relação de suporte mútuo, pois quanto melhor a comunicação, mais claro é aos
desenvolvedores o que exatamente precisa ser feito. E quanto mais simples o software,
menos o desenvolvedor precisa comunicar sobre seu comportamento, o que tende a
levar a uma comunicação mais completa (BECK e ANDRES, 2007).
iii. Feedback: No desenvolvimento de software quanto mais cedo uma falha é
detectada, mais barata é sua correção (FEITOSA, 2007). Portanto, o XP é organizado
em ciclos curtos de feedback do cliente, onde o objetivo é apresentar uma nova
funcionalidade ao usuário, de modo que ele possa o mais breve possível detectar
eventuais falhas (TELES, 2005). Feedback por sua vez contribui diretamente com
Comunicação e Simplicidade, pois quanto mais feedbacks, mais fácil de se comunicar
(BECK e ANDRES, 2007).
iv. Coragem: Desenvolvedores XP partem do princípio de que problemas irão
ocorrer, inclusive aqueles mais temidos, e ter coragem em XP significa ter confiança
nos mecanismos de segurança utilizados para proteger o projeto, mecanismos estes
que ajudarão a reduzir ou eliminar as consequências destes problemas (TELES, 2005).
Entretanto, valores são abstratos demais para guiar comportamentos. Eles indicam o
propósito, algo em que se acredita, a razão pela qual age-se de determinada forma (TELES,
2008). Práticas, por sua vez, são aquilo que efetivamente se faz no dia a dia, guiando-se por
um conjunto de valores e princípios. Identificá-las é útil pois são claras e objetivas (TELES,
2008).
O XP possui uma série de práticas absolutas que servem como um portfólio de ações a
disposição de uma equipe ágil de desenvolvimento. Segundo TELES (2008), aplicar essas
práticas ou não, é uma escolha diretamente dependente da situação, do contexto. Se dada
situação muda, seleciona-se práticas diferentes para abordar estas condições de mudança.
16
TELES (2008) também sugere que as práticas do XP sejam experimentadas como uma
hipótese, onde aplica-se determinada prática, verificando-se o quanto esta prática ajuda no
resultado do projeto. TELES (2008) complementa que embora esse conjunto de práticas tenha
sido concebido para funcionar bem em conjunto, sua adoção uma a uma é o suficiente para se
perceber benefícios. A medida que mais práticas passam a ser utilizadas em conjunto, mais
nítidas são as melhorias.
As práticas do XP são divididas entre: (i) primárias e (ii) corolárias (TELES, 2008). As
práticas primárias são aquelas que podem gerar benefício imediato independente de qualquer
outra que esteja-se utilizando. Já as corolárias tendem a ser mais difíceis de dominar sem que
antes tenha-se colocado em uso as práticas primárias.
2.3. TEST-DRIVEN DEVELOPMENT
O Desenvolvimento Guiado por Testes é uma das práticas primárias do XP e é nela que este
trabalho irá se focar. Seu princípio básico é a inversão da sequência tradicional de: (i) projetar,
(ii) implementar e enfim (iii) testar (SANTOS, 2010). Na prática, o desenvolvimento com
TDD é constituído de pequenas iterações onde casos de testes são escritos contemplando uma
nova funcionalidade, e depois, o código necessário para passar esses casos de teste é
implementado. Diante de eventuais mudanças, basta que o software seja software seja
refatorado de forma que os testes continuem passando (FEITOSA, 2007).
Cada iteração possui um micro objetivo, que terá sido alcançado quando os testes escritos
antes da implementação passarem. Dessa forma, o escopo da tarefa na qual o desenvolvedor
deverá se focar fica reduzido, ao passo que se dedicará ao seu micro objetivo ao invés de se
preocupar com todo software (FEITOSA, 2007), forçando o desenvolvimento a acontecer de
forma e incremental (SANTOS, 2010).
“TDD não é sobre testes, é sobre como usar testes para criar software de maneira simples e incremental. Além de melhorar a qualidade e o projeto do software, ele também simplifica o processo de desenvolvimento” (GOLD et al., 2004, apud SANTOS, 2010)
Tal simplificação do processo de desenvolvimento, contribui para um aumento no nível de
confiança do desenvolvedor no produto sendo construído SANTOS (2010).
17
2.3.1. Ciclo de desenvolvimento com TDD
Como citado anteriormente nesta seção, o desenvolvimento com TDD é constituído de
iterações. Kent Beck (BECK, 2003) resume o ritmo de desenvolvimento com TDD em 5
passos:
i. Escrever um teste novo;
ii. Rodar todos os testes e ver que o novo teste falha;
iii. Implementar o código necessário e suficiente para que o teste passe;
iv. Rodar todos os testes e ver que todos passam;
v. Refatorar o código implementado para remover duplicações.
2.4. A LINGUAGEM JAVASCRIPT
Desenvolvida pela Netscape Communications em 1994, o JavaScript é uma linguagem de
script massivamente utilizada para dar mais dinamismo à páginas da web. Ela permite ao
desenvolvedor manipular os elementos de uma página (HTML e CSS) para criar efeitos e
aplicar conteúdo dinâmico.
A linguagem de marcação HTML é utilizada para ajustar o conteúdo e a formatação de uma
página na web, a linguagem de estilo CSS para estilizar como este conteúdo deve ser exibido,
e o JavaScript é utilizado para criar efeitos e aplicações RIA (do inglês, Rich Internet
Application, ou Aplicações de Internet Rica). Na prática, o JavaScript possui métodos que
permitem aos desenvolvedores interagir com o HTML e o CSS para criar tais aplicações
(MOZILLA, 2012). O JavaScript é constituído por três elementos, o ECMAScript, o DOM
(do inglês, Document Object Model) e o BOM (do inglês, Browser Object Model).
2.4.1. ECMAScript, o núcleo da linguagem JavaScript
O núcleo da linguagem JavaScript é padronizado pelo comitê ECMA TC-39 sob o nome de
ECMAScript (MOZILLA, 2012). É este padrão que define a sintaxe do JavaScript, suas
palavras chaves, palavras reservadas, o controle de fluxo, tipos de dados, objetos, operadores,
18
dentre outros elementos. Atualmente o ECMAScript está em sua verão 5, porém a maioria dos
navegadores modernos implementam a versão 3, com algumas partes apenas da versão 5
(MOZILLA, 2012).
Além do JavaScript, outras linguagens também utilizam o ECMAScript como base, como o
Action Script, por exemplo.
2.4.2. DOM
O DOM é uma API (do inglês, Application Programming Interface) para manipulação de
documentos XML e que foi estendida para documentos HTML. Essa API mapeia os
documentos em uma hierarquia de nós, conhecida como DOM Tree, ou Árvore DOM, onde
cada parte de um documento é representado por um tipo de nó contendo um tipo diferente de
dado (ZAKAS, 2009).
Figura 1 - Exemplo de documento HTML
19
Figura 2 - Árvore DOM da figura anterior
A API do DOM permite aos desenvolvedores manipular toda a árvore, permitindo que
qualquer nó seja facilmente removido, adicionado ou modificado.
2.4.3. BOM
O BOM (do inglês, Browser Object Model) provê uma API que expõe elementos do
navegador que estão fora do contexto da página sendo exibida. Segundo ZAKAS (2009), esta
é a única parte da implementação do JavaScript que não é regulamentada, e devido a isso,
cada navegador possui sua própria implementação, o que o torna potencialmente
problemático.
Basicamente, o BOM engloba as janelas e os frames do navegador, vem dele a capacidade de
abrir janelas pop up, a capacidade de mover, redimensionar e fechar janelas, o suporte a
cookies e etc. Além disso, ele disponibiliza alguns objetos que proveem informações
detalhadas sobre o navegador, sobre a página sendo exibida e sobre a tela do usuário
(ZAKAS, 2009).
2.4.4. Características da linguagem
Como principais características da linguagem JavaScript, pode-se citar:
20
i. É imperativa e estruturada
ii. Possui tipagem fraca e dinâmica
iii. É baseada em objetos (ou baseada em protótipos)
iv. É interpretada, não compilada
v. Permite funções aninhadas
vi. Permite funções anônimas
vii. Possui suporte a expressões regulares
2.4.5. Testes unitários em JavaScript
Segundo Wells (1999), testes unitários são um dos alicerces do Extreme Programming. Em
um projeto de software, todas as classes do sistema deveriam ser testadas no nível unitário, e
qualquer código sem tal cobertura mínima de testes não deveria ser considerado pronto para
produção (Wells, 1999).
Cunningham (2005) define uma unidade de software como sendo a menor parte possível de
ser testada isoladamente em um sistema, onde tal isolamento significa que dada unidade possa
ser testada separada do resto da aplicação e de todas as demais unidades.
Conforme Wells (1999), ao criar os testes antes do código, espera-se que o desenvolvedor
encontre mais facilidade ao criar o código. Wells (1999) afirma que o tempo necessário para
criar um teste unitário e logo em seguida criar uma implementação para que o teste passe, é
praticamente o mesmo quando iniciando-se pela implementação.
Os testes unitários ajudam o desenvolvedor a pensar no que de fato deve ser feito, ou seja, os
requisitos do software são satisfeitos sob uma base forte de testes que garantem o
comportamento requerido (Wells, 1999).
Outro benefício dos testes unitários é o feedback imediato enquanto se desenvolve. Em
muitos casos, não fica claro quando o desenvolvedor terminou de fato toda a funcionalidade
necessária, principalmente quando dada funcionalidade passa por constantes mudanças de
escopo. Ao se criar testes unitários antes da implementação, fica claro ao desenvolvedor
quando a funcionalidade está pronta: quando todos os testes passarem (Wells, 1999).
21
Enquanto que em metodologias tradicionais de desenvolvimento de software, primeiro
constrói-se o software e depois testa-se o que foi construído, no XP, por utilizar-se o test first
(criação dos testes antes mesmo da implementação), todo o design do software é influenciado
pelo desejo de se testar tudo o que de fato gera valor para o cliente, deixando todo o sistema
mais facilmente testável do ponto de vista de negócios (Wells, 1999).
A maior resistência em se dedicar tanto tempo aos testes unitários são os curtos prazos para o
desenvolvimento do software. No entanto, o custo da criação antecipada de testes
automatizados não se compara ao custo de se criar testes após a descoberta de bugs (Wells,
1999). Wells (1999) afirma que o retorno que os testes automatizados proporcionam é muito
maior que o custo de criá-los. Além disso, os testes automatizados reduzem as chances de um
novo bug ser introduzido durante eventuais refatorações, pois a cada mudança nas unidades
do sistema é possível ter-se feedback rápido se essa mudança afetou alguma funcionalidade
existente do sistema (Wells, 1999), entretanto, a adição de novas funcionalidades ao sistema
geralmente exige que os testes unitários sejam adaptados para refletir a nova funcionalidade.
22
CAPÍTULO 3. FRAMEWORKS DE TESTES PARA JAVASCRIPT
Código JavaScript escrito para aplicações web tende a ter muitas dependências, pois
JavaScript puro geralmente só é útil quando combinado com HTML e CSS e por meio do uso
do DOM e do BOM (ZAKAS, 2009).
Por este motivo, além de o desenvolvedor ter de se atentar às diferenças entre engines
JavaScript, é necessário também se atentar às diferenças na forma em que uma página é
renderizada e como os elementos DOM podem ser manipulados entre diferentes ambientes
(ZAKAS, 2009). Isso por isso só já torna a tarefa de testar código JavaScript bastante onerosa.
Testes unitários, por definição, devem ser implementados com base no menor elemento
testável da funcionalidade do software a ser testada, e implica em testar a estrutura interna
(fluxo lógico e de dados) sem nenhuma dependência externa (ZAKAS, 2009). Nesse contexto,
os frameworks de testes auxiliam o desenvolvedor na preparação do ambiente propício aos
testes, com o devido isolamento da unidades a serem testadas, a um baixo custo de
manutenção.
3.1. xUNIT TEST FRAMEWORKS
Um tipo de framework bastante popular entre os desenvolvedores é o xUnit. O termo xUnit
refere-se a frameworks baseados nos conceitos do JUnit, o mais popular framework de testes
para Java e o SUnit, o framework de testes do Smalltalk (ZAKAS, 2009). Kent Beck, o pai do
Extreme Programming, é também um dos responsáveis pela criação de ambos frameworks.
Atualmente, existem diversas variações de xUnit para outras linguagens, como por exemplo o
nUnit, para C# e o JSUnit, para JavaScript. Embora o xUnit possa ser considerado o tipo mais
popular de framework de testes, os frameworks de BDD (do inglês, Behaviour-Driven
Development, em português, Desenvolvimento Guiado por Comportamento) teve um forte
crescimento em termos de popularidade nos últimos anos (ZAKAS, 2009).
23
3.1.1. BDD
Zakas (2009) afirma que a prática do TDD não está só relacionada ao uso de testes
automatizados, mas muito além disso, está relacionada ao design do software. Entretanto,
Zakas (2009) também afirma que devido a terminologia utilizada para descrever este
processo, muitos desenvolvedores acabam por se limitar a utilizar testes unitários para
verificar o funcionamento do sistema, e por esse motivo, nunca experimentaram de fato o
benefício de se usar os testes automatizados como ferramenta de design.
É nesse contexto que surge o BDD, que introduz um vocabulário mais rico e focado nos
aspectos comportamentais do sistema. Este vocabulário pode facilmente ser compartilhado
entre desenvolvedores, analistas de testes e de negócio, e clientes. Este vocabulário permite
que os aspectos importantes do sistema sejam transmitidos de forma satisfatória entre os
envolvidos no desenvolvimento de uma funcionalidade, permitindo que requisitos e
problemas sejam discutidos mais facilmente.
O processo de desenvolvimento com BDD se baseia em "estórias", as quais descrevem uma
funcionalidade que agregue valor ao cliente (Cohn, 2004), e que utilizam um vocabulário
familiar a todos os envolvidos no projeto (ZAKAS, 2009). O Cucumber, um dos frameworks
BDD mais populares, permite ao desenvolvedor utilizar estórias como testes executáveis, o
que permite que os testes aceitação sejam construídos junto ao cliente, aumentando as chances
de que as expectativas do cliente sejam completamente alcançadas com a entrega do software
(ZAKAS, 2009).
3.2. ESTRUTURA BÁSICA DE UM FRAMEWORK DE TESTES
Frameworks de testes tem uma estrutura bastante similar, independente do tipo de framework.
A estrutura básica de um framework de testes é composta pelos elementos a seguir.
3.2.1. Caso de teste, ou cenário de teste
Cada teste unitário é um caso de teste focado em um aspecto específico do comportamento
esperado de uma classe. Estes testes são na verdade métodos que irão exercitar o código
sendo testado, garantindo que determinada entrada de dados produzirá sempre a mesma saída
24
esperada. Por exemplo, o método de teste chamado testSomar() seria o teste unitário para o
método somar() da classe Calculadora.
Para garantir que o código testado produzirá sempre a mesma saída, dado o mesmo contexto,
são utilizadas fixtures para se criar o contexto do teste, e asserções para avaliar o resultado
obtido.
3.2.1.1. Asserções
Os frameworks xUnit permitem ao desenvolvedor criar um conjunto de métodos de teste
que podem ser executados e irão gerar um relatório da execução, exibindo as falhas nos
testes quando houver. O coração dos métodos de teste dos xUnit são as asserções, que
são chamadas de método que servem para garantir que o resultado da execução de
determinada parte do código produziu a saída esperada.
Um método de asserção bastante comum nos frameworks xUnit é o “assert equals“, que
possui dois parâmetros obrigatórios; o primeiro é o valor esperado, o segundo é o valor
atual, que geralmente é o retorno de uma a expressão. Caso o valor dos dois parâmetros
sejam diferentes entre si, uma exceção é lançada, quebrando o teste em questão (Zakas,
2009).
Figura 3 - Exemplo de um “assert equals” no framework JUnit, para Java
A sintaxe deste método possui inúmeras variações de acordo com a linguagem e o
framework utilizados. Asserções serão abordadas mais afundo no Error! Reference source Error! Reference source Error! Reference source Error! Reference source
not found.not found.not found.not found.
3.2.2. Fixtures
As fixtures separam o contexto de um teste. É uma convenção que os frameworks
disponibilizem métodos opcionais para que o desenvolvedor preencha as pré-condições de um
25
teste. Esses métodos são conhecidos como setUp() e tearDown(), onde o primeiro é usado
para criar o contexto do teste e o segundo é usado para retornar o ambiente ao seu estado
original. Ambos os métodos são independentes, ou seja, o desenvolvedor pode escolher
utilizar apenas o método setUp() ou vice-versa.
Embora a nomenclatura destes métodos varie de acordo com o framework, e em alguns casos
existam mais métodos além destes dois, o conceito de setUp e tearDown permanece o mesmo.
Em testes unitários de JavaScript, é comum que uma fixture seja uma extração de código
HTML com o qual o código JavaScript irá interagir.
3.2.3. Suíte de testes
Uma suíte de testes geralmente engloba os cenários de teste que utilizam a mesma fixture, no
entanto, em alguns frameworks é possível se utilizar em uma mesma suíte, vários conjuntos
de cenários, cada conjunto com suas fixtures.
Na prática, uma suíte de testes é um arquivo com a mesma extensão de uma classe da
linguagem sendo utilizada (.java para Java, .js para JavaScript, e etc), e que possui vários
métodos de teste (casos de teste). Por exemplo, o arquivo TestCalculadora.java seria uma suíte
de testes unitários para a classe Calculadora, e dentro desse arquivo, estariam os métodos
testSomar(), testDividir(), e etc, e opcionalmente os métodos setUp() e tearDown().
3.3. TEST RUNNERS
Zakas (2009) afirma que a parte mais importante em uma ferramenta de testes é o test runner,
pois é ele que determina o workflow quando se está desenvolvendo guiado por testes. O papel
do test runner é basicamente executar todo o código de teste, criado utilizando-se um
framework de testes unitários, e após a execução, apresentar um relatório da mesma.
Feedback rápido e claro sobre o resultados dos testes é uma exigência quando se está
trabalhando com testes unitários. O desenvolvedor precisa ter feedback rápido, pois o
processo de execução dos testes é algo que acontece inúmeras vezes durante o workflow, e
quando os testes falham, é necessário que o relatório de erros apresente o nível de detalhes
necessário para que o desenvolvedor identifique de forma rápida qual o motivo da falha.
26
Geralmente os frameworks da família xUnit possuem uma barra de progresso que mantem-se
verde enquanto os testes sendo executados estão passando, e a partir da primeira falha, ou
erro, a cor muda para vermelho, indicando que há um problema. A partir daí, o desenvolvedor
pode visualizar mais detalhes sobre a mesma, geralmente contendo a saída esperada e a saída
atual, ou uma stacktrace, em caso de exceções não tratadas (o caminho de uma exceção não
tratada através das camadas da aplicação).
No caso dos testes em JavaScript, existem dois tipos de test runners, os runners “in-
browser”, que são aqueles executados dentro de uma instância do navegador, e os runners
headless, que são aqueles que executam os testes em modo linha de comando, sem abrir uma
instância real de um navegador.
3.3.1. Runners “In-Browser”
Muitos frameworks possuem um runner in-browser nativo. Basicamente, o test runner é
invocado através de uma página HTML que carrega o framework e os testes unitários a serem
executados. Assim, o desenvolvedor pode acessar essa página no navegador desejado e
verificar se seu código JavaScript funciona naquele navegador.
A grande vantagem desses test runners é o fato de os testes rodarem em uma instância real do
navegador e a facilidade com que o desenvolvedor pode verificar o funcionamento do código
testado em diferentes navegadores e versões. Em contrapartida, abrir uma instância do
navegador e carregar manualmente a página do runner nos navegadores desejados é uma
tarefa onerosa e não atende a necessidade de feedback rápido no workflow com TDD.
Entretanto, é possível automatizar este processo.
Alguns exemplos frameworks de testes que possuem test runner in-browser nativo são o YUI
Test, mantido pelo time de desenvolvimento do Yahoo! (YAHOO, 2012) e o QUnit, mantido
pelo time de desenvolvimento do jQuery (JQUERY, 2012).
3.3.2. Runners Headless
Test runners headless por sua vez, não instanciam nenhum navegador, ao invés disso,
disponibilizam um utilitário de linha de comando que simula o comportamento de um
27
navegador real. O funcionamento deste tipo de test runner é muito semelhante aos dos
frameworks de testes das linguagens server-side.
As principais vantagens deste tipo de runner são (i) a velocidade com que os testes são
executados e (ii) a facilidade com que a execução dos testes pode ser automatizada e integrada
a um ambiente de Integração Contínua.
Um exemplo de runner headless é o env.js (ENVJS, 2012), desenvolvido originalmente pelo
criador do framework jQuery. Este runner possui sua própria implementação de navegador
(APIs DOM e BOM, etc) baseada no Rhino (MOZILLA, 2012b), uma implementação do
JavaScript em linguagem Java (ZAKAS, 2009). A combinação env.js + Rhino permite que os
testes de JavaScript sejam executados via linha de comando sem a utilização de um navegador
real.
Um problema apresentado por Zakas (2009) é que utilizando-se essas implementações, os
testes serão executados em um ambiente muito diferente de um ambiente de produção (os
navegadores dos usuários finais, por exemplo). Não somente o DOM é emulado, mas também
o JavaScript utilizado é uma implementação diferente. Sendo assim, essa combinação por si
só não é suficiente para garantir que o código alvo de testes irá funcionar em qualquer
navegador real.
28
CAPÍTULO 4. BUSTER.JS
Testes in-browser são difíceis de integrar a um workflow de desenvolvimento guiado por
testes, e testes headless por sua vez são mais fáceis de se trabalhar, neste contexto. No
entanto, testes headless geralmente não dão muita segurança quanto ao funcionamento em um
ambiente real (ZAKAS, 2009).
O Buster.js (BUSTERJS, 2012) é um framework de testes e test runner que se encontra em
fase beta, mas que já se mostra eficiente em abordar ambos os modos de execução, em um
ambiente de fácil configuração e poucas dependências externas.
O Buster.js é um pacote para o Node.js (NODEJS, 2012), um ambiente JavaScript para
servidores, portanto não é necessário baixar nenhum arquivo JavaScript, nem referenciar
nenhum arquivo JavaScript do Buster em páginas HTML. A única dependência deste
framework, é o Node.js instalado. Entretanto, é também possível utilizar o Buster.js como um
arquivo JavaScript simples, sem a necessidade de instalação do Node.js, como apresentado na
seção 4.2.
A sintaxe do Buster.js é muito similar à do RSpec, o popular framework BDD para Ruby. Sua
configuração é simples e sua sintaxe não requer que o desenvolvedor constantemente
referencie módulos nativos da linguagem, e isso deixa o código do teste mais limpo.
Figura 4 - Exemplo de caso de teste em Buster.js
A Figura 4 é um exemplo de um caso de teste simples. Em Buster.js, casos de teste são
chamadas ao método testCase do objeto buster, que por sua vez é uma instância do
framework de testes.
Como mostrado na Figura 4, o método testCase recebe como primeiro parâmetro o nome uma
String, que geralmente contém apenas o nome da classe alvo do teste, a partir daí, o
29
desenvolvedor adiciona os métodos de teste e os métodos setUp e tearDown, todos separados
por vírgula.
A Figura 5 e a Figure 6 mostram um caso de teste para uma classe Calculadora, escrita em
JavaScript. Por motivos didáticos, a classe Calculadora possui apenas o método soma, e
recebe apenas dois parâmetros, que são os números que devem ser somados.
Figura 5 – Calculadora.js: Classe Calculadora, em JavaScript
Figure 6 – Calculadora-test.js: Teste da class Calculadora, em JavaScript
Na Figure 6, a primeira linha contém uma variável declarada no escopo global do arquivo
Calculadora-test.js, essa variável calc é initializada no método setUp com uma instância da
classe Calculadora. Por ter sido declarada em um escopo global, essa variável estará acessível
a todos os métodos de testes que forem criados nesta classe de teste.
O arquivo Calculadora-test.js contém um único teste, que testa o funcionamento do método
soma da Calculadora apresentada na Figura 5.
4.1. INSTALAÇÃO E CONFIGURAÇÃO
Por ser um pacote para Node.js, sua instalação é bastante simples e possível por meio do
utilitário de linha de comando npm, que é um gerenciador de pacotes para Node.js. Sendo
assim, para instalar o Buster.js, utiliza-se o comando npm install -g buster em um prompt de
linha de comando. A instalação do Node.js não será abordada neste trabalho.
30
O Buster.js requer apenas um arquivo de configuração, onde são apontados os diretórios que
contém as classes de teste, as classes testadas, e quaisquer outras bibliotecas que sejam
dependências para os testes.
Figure 7 - Exemplo de estrutura de um projeto
A Figure 7 mostra a estrutura de um projeto de exemplo. A pasta src contém as classes de
negócio, e a pasta test contém as classes de teste. Segundo a convenção do Buster.js, para
cada classe X.js, haverá uma classe teste X-test.js
Para que o Buster.js consiga carregar as classes de suas pastas corretas, é necessária a
presença do arquivo arquivo de configuração test/buster.js. O conteúdo deste arquivo é
mostrado na Figura 8.
Figura 8 – Arquivo de configuração do Buster.js
No arquivo de configuração apresentado na Figura 8, rootPath, na linha 4, diz respeito ao
diretório base, relativo ao arquivo atual (test/buster.js), de onde os demais arquivos a serem
referenciados serão carregados. Caso omitido, é necessário adicionar o caminho relativo em
cada entrada de diretório subsequente (../lib, ../src, etc).
Na linha 5, environment diz respeito ao ambiente no qual os testes serão executados. Este
ambiente pode ser browser ou node. A diferença será explorada nos items a seguir.
31
Na linha 6, libs indica de onde o Buster.js irá carregar quaisquer bibliotecas que são
dependência para os testes, por exemplo, o jQuery.
Nas linhas 7 e 8, sources e tests indicam o local onde se encontram as classes de negócio e as
classes de testes respectivamente.
Após configurado, para executar os testes, basta executar o comando buster test a partir da
raíz do projeto, que no exemplo da Figure 7 seria a pasta meu_projeto_js.
4.2. MODO IN-BROWSER
O Buster.js oferece três diferentes formas de se executar testes in-browser, descritas a seguir.
4.2.1. Modo HTML
A forma mais simples de se executar testes no Buster.js é criando-se um documento HTML,
onde dentro de tags script, o desenvolvedor referencia a biblioteca do Buster.js e todos os
arquivos de teste, não sendo necessária a criação de um arquivo de configuração do Buster,
como mostrado na Figura 10. O exemplo desta seção utiliza as classes Calculadora.js e
Calculadora-test.js das figuras Figura 5 e Figure 6.
Figura 9 - Organização das classes e testes
32
Figura 10 - Arquivo buster.html
Feito isso, basta abrir o arquivo buster.html no navegador desejado para visualizar o relatório
dos testes, onde pode-se ver a quantidade de testes executados, a quantidade de sucessos,
asserções, falhas, erros, timeouts e o tempo total de execução, como mostrado na Figura 11.
Figura 11 - Relatório de execução dos testes
A Figura 12 apresenta um exemplo de falha durante a execução dos testes. É possível além da
contagem de sucessos e falhas, uma stack trace da falha, para auxiliar o desenvolvedor na
identificação o problema.
33
Figura 12 - Resultado de execução com falha
Embora este modo seja o modo mais simples e rápido de se obter feedback sobre os testes, ele
é demasiado manual para um workflow com TDD. Além disso, caso o desenvolvedor quisesse
verificar o funcionamento em outro navegador, seria necessário abrir o arquivo buster.html
manualmente no navegador desejado.
É importante ressaltar que o exemplo da calculadora não faz uso do DOM, então não haveria
muito ganho em se executar estes testes em diferentes navegador. Mais adiante será
apresentado um exemplo mais completo, em que o feedback em diferentes navegadores se
torna mais importante.
4.2.2. Modo estático
O modo estático é bastante parecido com o modo anterior, a diferença é que no modo estático
uma página HTML parecida com a da Figura 11 é remotamente servida pelo endereço
http://<url do servidor>:8282.
34
Para utilizar este modo, é necessário criar um arquivo de configuração como o da Figura 8 e
em um terminal de linha de comando, executar o comando buster static, como mostra a
Figure 13.
Figure 13 - Comando buster static
Acessando a url http://localhost:8282, tem-se a mesma página HTML da Figura 11. Neste
modo, não é necessário criar manualmente o arquivo buster.html mostrado na Figura 10, pois
a página com os resultados é gerada automaticamente pelo Buster.js.
Entretanto, este modo ainda não é o ideal em um workflow com TDD, pois de forma
semelhante ao modo HTML, no modo estático o desenvolvedor precisa abrir manualmente a
url com o relatório ons navegadores desejados e recarregar a página a cada alteração no
código.
4.2.3. Modo servidor
O modo server permite a automatização dos testes em diferentes navegadores. Este modo se
baseia no conceito master x slave, onde diferentes navegadores, em diferentes versões e
sistemas operacionais podem ser utilizados como slaves, onde os testes serão executados cada
vez que o desenvolvedor executar o comando buster test.
Nesta seção, será utilizado como exemplo uma classe JavaScript para validação de
formulários. Foi criado um formulário de login, com os campos usuário e senha, em que
apenas o campo usuário é obrigatório. A Figura 14 apresenta a estrutura do projeto utilizado
no exemplo.
35
Figura 14 - Estrutura do exemplo validação de formulário
Na Figura 14 pode-se observar o diretório test/fixtures. Este diretório contém os arquivos
HTML que serão utilizados nos testes. Estes arquivos de fixtures contém apenas uma extração
do HTML da página real, o suficiente para que o JavaScript sob teste possa ser exercitado por
completo. A Figura 15 exibe o conteúdo da fixture formulario.html.
Figura 15 - Conteúdo do arquivo test/fixtures/formulario.html
Para configurar o Buster.js de forma que as fixtures sejam carregadas corretamente, é
necessário adicionar a entrada resources ao arquivo buster.js, como mostra a Figura 16 na
linha 9.
36
Figura 16 - Configuração do Buster.js para uso de fixtures
Para que a classe de validação da Figura 15 funcione, é necessário que o formulário a ser
validado possua o atributo validate=”true”, e os campos obrigatórios precisam ter a classe
required como mostrado na linhas 1 e 3 da Figura 15, respectivamente.
Outro ponto a se observar na Figura 14 é a presença do arquivo jquery.min.js no diretório lib.
Este arquivo é necessário para que seja possível utilizar os helpers do jQuery para
manipulação do DOM. A Figura 17 apresenta a classe de validação criada para o exemplo em
questão.
37
Figura 17 - Conteúdo da classe ValidaForm.js
As figuras a seguir ilustram o efeito da classe de validação da Figura 17. Na Figura 18 é
apresentado o formulário em seu estado original, e na Figura 19, o mesmo formulário após
detectado erro na validação.
Figura 18 - Formulário de login do arquivo test/fixtures/formulario.html
38
Figura 19 - Formulário de login apresentando erro
O intuito da classe de validação é fazer com que os formulários que possuem o atributo
validate=”true” (Figura 18, linha 16) sejam validados ao clicar no botão submit, e fazer com
que quaisquer campos que possuam a classe required sejam validados ao deixar o campo
(evento blur, Figura 18, linha 23). O teste para a classe ValidaForm.js é apresentado a seguir,
na Figura 20.
Figura 20 - Clase de teste ValidaForm-test.js
39
Para automatizar a execução do teste da Figura 20, é necessário iniciar o serviço do Buster.js
e capturar os navegadores onde se deseja que os testes sejam executados. A inicialização o
serviço do Buster se dá através do comando buster server em um terminal de linha de
comando, como mostra a Figura 21.
Figura 21 - Comando buster server
A partir daí, para que o navegador desejado seja capturado. É necessário acessar a url exibida
na Figura 21 e clicar no botão de captura, como mostra a Figura 22.
Figura 22 - Página de captura de slaves
O inconveniente deste modo, é a necessidade de acessar a página de captura em todos os
navegadores desejados e capturá-los um a um. Porém, após feito isso, basta executar o
comando buster test a partir do diretório raiz do projeto e ver o resultado no terminal de linha
de comando, como mostra a Figura 23.
40
Figura 23 - Relatório da execução dos testes
É possível se observar no relatório de execução, em quais navegadores os testes foram
executados e em quais sistemas operacionais. No caso da Figura 23, os testes rodaram nas
versão 19 do navegador Firefox, na versão 25 do navegador Chrome, e na versão 6 do
navegador Safari. Caso não hajam slaves capturados, uma mensagem informativa é exibida,
como mostra a Figura 24.
Figura 24 - Tentativa de execução dos testes sem slaves capturados
A Figura 25 mostra um exemplo de execução com falhas.
Figura 25 - Relatório de execução com falhas
Uma grande vantagem deste método é a fácil integração no workflow com TDD, onde a cada
alteração de código, e a cada vez que o desenvolvedor desejar ter feedback sobre o
41
funcionamento do mesmo em diferentes navegadores, basta invocar o comando buster test e
aguardar a execução terminar.
4.3. MODO HEADLESS
Em um modo headless, os testes são executados sem que seja necessária nenhuma
instância real de um navegador, como descrito no item 3.3.2. No entanto, como citado no
início deste capítulo, o Buster.js encontra-se em versão beta, e na versão atual, está
funcionalidade ainda não está disponível, embora a própria documentação oficial apresenta
métodos alternativos.
4.4. MODO NODE
Além do modo headless, o modo Node permite que os testes sejam executados sem a presença
de navegadores, no entanto, neste modo o cenário de teste de fato não requer a presença de
um navegador, como no exemplo do teste da calculadora do item 4.2.1.
Para se executar os testes neste modo, algumas alterações são necessárias ao arquivo de
configuração do Buster.js. A primeira delas é alterar o valor da entrada environment, de
browser para node, como mostra a Figura 26, na linha 6.
Figura 26 - Configuração do Buster.js no modo node
Neste modo é necessário apenas indicar o diretório onde estão os arquivos de teste, não sendo
necessário utilizar as entradas lib e src (conforme visto na Figura 8), pois as classes que
forem dependência para os testes serão carregadas diretamente nos testes, utilizando o método
require, do Node.js. Até mesmo a biblioteca do Buster precisa ser carregada através do
método require, como pode ser visto na linha 2 da Figura 26.
42
São necessárias também mudanças tanto nas classes de negócio quanto nas classes de teste,
como mostram as figuras Figura 27 e Figura 28. Nas classes de negócio, é necessário utilizar
o método module.exports, para que a classe fique disponível fora do contexto do arquivo em
que a classe foi definida (Figura 27).
Figura 27 - Classe Calculadora.js alterada para o modo node
Nas classes de teste é necessário utilizar o método require para se carregar a classe de negócio
sendo testada (Figura 28).
Figura 28 Classe Calculadora-test.js alterada para o modo node
4.5. ASSERÇÕES
Os exemplos apresentados até aqui contém apenas o método de asserção assert.equals. No
entanto, o Buster.js conta com uma variada gama de métodos de asserção, que visam dar mais
valor semântico aos testes. Os principais métodos de asserção do Buster.js são brevemente
apresentados a seguir.
43
4.5.1. assert.same()
O método assert.same(objeto atual, objeto esperado) falha caso os dois objetos sendo testados não sejam referências à mesma instância.
4.5.2. assert.equals()
O método assert.equals(objeto atual, objeto esperado) falha caso os dois objetos sendo
testados não tenham o mesmo valor (em caso de valores primitivos), ou seus atributos
apresentem alguma diferença.
4.5.3. assert.greater()
O método assert.greater(valor atual, valor esperado) espera que o primeiro parâmetro seja
um valor maior que o segundo parâmetro.
4.5.4. assert.less()
Ao contrário do item anterior, o método assert.less(valor atual, valor esperado) espera que
o primeiro parâmetro seja menor que o segundo parâmetro.
4.5.5. assert.defined()
O método assert.defined(objeto) falha caso o objeto sendo testado seja undefined.
4.5.6. assert.isNull()
O método assert.isNull(objeto) falha caso o objeto sendo testado não seja null (nulo).
4.5.7. assert.isObject()
O método assert.isObject(objeto) espera que o parâmetro passado seja um objeto não nulo.
44
4.5.8. assert.isFunction()
O método assert.isFunction(função) espera que o parâmetro passado seja uma função.
4.5.9. assert.isTrue()
O método assert.isTrue(valor ou expressão) espera que o parâmetro passado resulte em um
valor true.
4.5.10. assert.isFalse()
Ao contrário do método anterior, este método espera que o parâmetro passado resulte em
um valor false.
4.5.11. assert.isString()
O método assert.isString(parâmetro) falha caso o parâmetro passado não seja do tipo
String.
4.5.12. assert.isBoolean()
O método assert.isBoolean(parâmetro) falha caso o parâmetro passado não seja um valor
booleano.
4.5.13. assert.isNumber()
O método assert.isNumber(parâmetro) falha caso o parâmetro passado não seja um valor
numérico.
45
4.5.14. assert.isNaN()
O método assert.isNaN(parâmetro) falha caso o parâmetro passado não seja um NaN.
4.5.15. assert.isArray()
O método assert.isArray(parâmetro) falha caso o parâmetro passado não seja um array.
4.5.16. assert.exception()
O método assert.exception(expressão) falha caso o parâmetro passado não gere uma
exceção.
4.5.17. assert.contains()
O método assert.contains(array, elemento) espera que o primeiro parâmetro contenha o
segundo parâmetro.
4.5.18. assert.tagName()
O método assert.tagName(elemento, nome da tag) falha caso o elemento passado no
primeiro parâmetro não possua a tag especificada no segundo parâmetro.
4.5.19. assert.className()
O método assert.className(elemento, nome classe) falha caso o elemento passado no
primeiro parâmetro não possua a classe especificada no segundo parâmetro.
Além dos métodos de asserção nativos do Buster.js, é possível criar novos métodos de
asserção e até mesmo sobrescrever os métodos existentes.
46
Este capítulo apresentou o framework Buster.js, primeiramente abordando a instalação e
configuração do mesmo e uma estrutura básica para seu funcionamento. A partir daí foram
abordados os diferentes modos de execução in-browser e uma alternativa ao modo headless,
utilizando-se Node.js, para os casos em que não é necessária a presença de um navegador.
47
CAPÍTULO 5. CONCLUSÃO
Este trabalho se propôs a investigar os fatores que tornam os testes automatizados em
JavaScript uma tarefa onerosa e propícia a erros. Para alcançar o objetivo, foi necessário
conhecer o estado atual dos frameworks de testes automatizados para JavaScript e verificar a
existência de uma ferramenta que se sobressaia às dificuldades encontradas e que seja útil no
fluxo de desenvolvimento com TDD.
Embora esteja em fase beta, o Buster.js se mostrou uma ferramenta bastante completa, por
cobrir diversos aspectos da execução de testes em JavaScript. O Buster.js possui test runners
nativos que permitem que os testes sejam rodados tanto em navegadores reais quanto sem a
presença de um navegador, quanto o código sendo testado não interage diretamente com o
DOM ou com o BOM.
No entanto, o Buster.js ainda não possui uma implementação que permite que testes que
interajam com o navegador, sejam executados em modo headless.
Um dos fatores que contribuem para que o Buster.js seja um framework tão completo, mesmo
estando em fase beta, é o fato de suas funcionalidades serem baseadas no funcionamento de
outros frameworks, como por exemplo o JsTestDriver, mas visando maior simplicidade e
flexibilidade.
Além de possuir uma sintaxe simples e bastante semelhante à de outros frameworks populares
de outras linguagens, o Buster.js, possui suporte ao estilo BDD de escrita de testes, além do
estilo TDD tradicional. Entretanto, apenas o estilo TDD foi apresentado neste trabalho.
Por fim, em relação a trabalhos futuros, considerando que a medida que o o número de testes
automatizados aumenta, maior o tempo de execução dos mesmos, um possível
direcionamento seria a busca por meios de execução dos testes em modo headless, por meio
da integração do Buster.js com outros test runners ou implementações headless do WebKit,
como o PhantomJS.
48
REFERÊNCIAS
BECK, K. et al. Manifesto for Agile Software Development. 2001. Disponível em: <http://www.agilemanifesto.org/>. Acesso em: 13 jan. 2012. BECK, K.; ANDRES C. Extreme Programming Explained, Embrace Change. 2. ed. Addison-Wesley Professional, 2004. 224.
BUSTERJS. Buster.js. 2012. Disponível em: <http://docs.busterjs.org/en/latest/overview>. Acesso em: 1 set. 2012. CIFANI, D. S. Introdução a testes automatizados em Ruby on Rails. 2011. Trabalho de Conclusão de Curso (Bacharel em Sistemas de Informação). Universidade Veiga de Almeida. 2011. COHN, M. User Stories Applied: For Agile Software Development. 2. ed. Addison-Wesley Professional, 2004. 304. CUNNINGHAM, W. Standard Definition Of Unit Test. 2005. Disponível em: <http://c2.com/cgi/wiki?StandardDefinitionOfUnitTest>. Acesso em: 29 ago. 2012.
ENVJS. Env.js. 2012. Disponível em: <http://www.envjs.com>. Acesso em: 1 set. 2012. FEITOSA, D. S. Um estudo sobre o impacto do uso de desenvolvimento orientado por testes na melhoria da qualidade de software. 2007. Trabalho de Conclusão de Curso (Bacharel em Ciências da Computação) Universidade Federal da Bahia. 2007. MOZILLA. JavaScript technologies overview. 2012. Disponível em: <https://developer.mozilla.org/en-US/docs/JavaScript_technologies_overview>. Acesso em: 29 ago. 2012. MOZILLA. Rhino. 2012. Disponível em: <https://developer.mozilla.org/en-US/docs/Rhino>. Acesso em: 1 set. 2012. NIST. The Economic Impacts of Inadequate Infrastructure for Software Testing. Disponível em: <http://www.nist.gov/director/planning/upload/report02-3.pdf>. Acesso em: 10 jan. 2012. NODEJS. Node.js. 2012. Disponível em: <http://nodejs.org>. Acesso em: 2 nov. 2012. PRESSMAN, R. S. Engenharia de Software. Makron Books, 2005.
JQUERY. QUnit: A JavaScript Unit Testing framework. 2012. Disponível em: <http://qunitjs.com>. Acesso em: 1 set. 2012.
49
SANTOS, R. L. Emprego de Test Driven Development no desenvolvimento de aplicacões. 2010. Trabalho de Conclusão de Curso (Bacharel em Ciencia da Computação). Universidade de Brasília. 2010. SOMMERVILLE, I. Software Engineering. Addison-Wesley Professional, 2003. TELES, V. M. Extreme Programming. 2008. Disponível em: <http://improveit.com.br/xp>. Acesso em: 13 jan. 2012. TELES, V. M. Desenvolvimento orientado a testes. 2008. Disponível em: <http://improveit.com.br/xp/praticas/tdd>. Acesso em: 13 jan. 2012. TELES, V. M. Um estudo de caso da adoção das práticas e valores do Extreme Programming. 2005. Dissertação (Mestrado em Informática) - Universidade Federal do Rio de Janeiro. 2005.
WELLS, D. Unit Tests. 1999. Disponível em: <http://www.extremeprogramming.org/rules/unittests.html>. Acesso em: 27 ago. 2012. ZAKAS, N. C. The curious case of JavaScript unit testing. 2009. Disponível em: < http://www.nczonline.net/blog/2009/11/17/the-curious-case-of-javascript-unit-testing>. Acesso em: 13 jan. 2012.
YAHOO. YUI Test. 2012. Disponível em: <http://yuilibrary.com/yui/docs/test/>. Acesso em: 1 set. 2012.
ZAKAS, N. C. Professional JavaScript for Web Developers. 2. ed. Wrox, 2009. 840.