1. Introdução
Este documento (última versão em http://paulojeronimo.github.io/javaee-tutorial-testes/ e também disponível em PDF) é um tutorial prático que apresenta conceitos e exemplos da aplicação de Test Driven Development (TDD) ou Behavior Driven Development (BDD) no desenvolvimento de aplicações Java EE. Ele é útil para o início de um estudo mais aprofundado sobre o framework Arquillian na produção de aplicações que integram soluções Java EE com frameworks como o AngularJS.
Este material utiliza scripts para que automatizam a montagem de um ambiente de desenvolvimento para aplicações Java EE e, embora não seja o foco prioritário deste material, ele também apresenta algumas soluções e exemplos sobre como realizar testes em aplicações Javascript, em especial as desenvolvidas com o AngularJS no frontend
e Java EE no backend
.
O conteúdo deste material foi projetado para ser apresentado, presencialmente por um instrutor, em até oito horas. Contudo, o estudo completo das referências citadas neste documento extrapola, e muito, esta carga horária. Portanto, fica cargo do estudante correr atrás de expandir seus conhecimentos através do aprofundamento do estudo dessas referências.
2. O que você precisa saber
Os roteiros deste material foram produzidos para a execução, inicialmente, num ambiente Linux (Fedora 21). Seu desenvolvimento foi realizado com base na instalação padrão de uma workstation
nessa versão do Fedora numa máquina real ou numa box
Vagrant. Contudo, esses roteiros também podem ser facilmente adaptados para a execução em outras distribuições Linux (como RHEL, CentOS, Debian ou Ubuntu) assim como no OS X e no Windows (num shell Cygwin). Para executar as tarefas deste material, espera-se que o estudante tenha facilidade para compreender e executar scripts no Bash.
Os exemplos de código apresentados neste material são, primariamente, escritos em Java e JavaScript. Obviamente, acredita-se que o estudante já tenha experiência nessas linguagens. Da mesma forma, o estudo detalhado das tecnologias testadas nos exemplos discutidos neste material está fora de seu escopo. Portanto, julga-se que o estudante já possua conhecimentos nas APIs e frameworks Java/Javascript que são alvo dos códigos de teste (exemplos: JPA, EJB, JAX-RS, AngularJS, etc).
Espera-se que o estudante, ao iniciar a leitura deste material, já possua algum embasamento em JUnit e em TDD. Caso este não seja o caso, é recomendado que o estudante faça a leitura das referências básicas (marcadas como "Ref. básica") apresentadas ao final deste documento.
3. Instalando e configurando o Fedora
Está fora do escopo deste material apresentar um passo a passo completo para a instalação do Fedora e, por isso, sugerimos a utilização do Vagrant caso haja dificuldades nessa instalação. Em todo caso, na Internet há vários materiais a respeito disso e, após instalado, o estudante deverá se certificar de ter realizado as configurações e instalações de pacotes recomendadas neste tópico caso não esteja utilizando o Vagrant.
3.1. Configurando o Fedora numa máquina real
3.1.1. Configurando o arquivo /etc/sudoers
Para que a execução de scripts dependentes do uso do comando sudo
não fique solicitando uma senha, é preciso que o arquivo /etc/sudoers
contenha linhas configuradas conforme a saída apresentada na execução do comando abaixo:
$ sudo grep wheel /etc/sudoers
## Allows people in group wheel to run all commands
#%wheel ALL=(ALL) ALL
%wheel ALL=(ALL) NOPASSWD: ALL
Para editar esse arquivo, deixando-o escrito da forma acima, execute:
sudo visudo
3.1.2. Trabalhando com o Fedora mais atual
Todos os roteiros deste tutorial foram desenvolvidos com a versão mais atual dos pacotes do Fedora. Então, para ter estas mesmas versões em teu ambiente, execute o update
dos pacotes. Se, antes desse passo, você desejar manter o cache dos pacotes que serão baixados em tua máquina, execute:
sudo sed -i 's/^\(keepcache=\)0/\11/g' /etc/yum.conf
Atualize os pacotes:
sudo yum -y update
Reinicie o Fedora (para termos certeza que estaremos utilizando o último kernel disponível):
sudo shutdown -r now
Após a reinicialização da máquina, observe a seleção (automática e configurada no grub) da última versão do kernel, no momento do boot.
Logue-se com o teu usuário e, para manter apenas o kernel mais novo na lista de opções do grub, execute:
sudo package-cleanup -y --oldkernels --count=1
3.1.3. Instalando pacotes contendo utilitários que serão executados pelos scripts deste tutorial
Os scripts que você executará mais a frente necessitam da instalação de alguns pacotes de utilitários. Então, execute a seguinte instrução:
sudo yum -y install vim redhat-lsb-core patch
3.1.4. Instalando o Oracle JDK
Alguns sites, como os do Banco do Brasil e do Itaú, dependem da instalação do Oracle JRE. Então, é interessante ter esta JRE instalada. E, apesar do OpenJDK fazer parte da instalação padrão do Fedora 21, utilizaremos o Oracle JDK, neste material.
Para instalar o Oracle JRE (e JDK) utilizaremos o Fedy, executando os comandos a seguir:
curl -sSL https://satya164.github.io/fedy/fedy-installer | sudo bash
sudo fedy -e oracle_jre oracle_jdk
Em seguida, configuraremos os binários que serão executados do Java, utilizando o comando alternatives
:
sudo alternatives --install /usr/bin/java java /usr/java/latest/jre/bin/java 200000
sudo alternatives --install /usr/bin/javaws javaws /usr/java/latest/jre/bin/javaws 200000
sudo alternatives --install /usr/lib64/mozilla/plugins/libjavaplugin.so libjavaplugin.so.x86_64 /usr/java/latest/jre/lib/amd64/libnpjp2.so 200000
sudo alternatives --install /usr/bin/javac javac /usr/java/latest/bin/javac 200000
sudo alternatives --install /usr/bin/jar jar /usr/java/latest/bin/jar 200000
sudo alternatives --set java /usr/java/latest/jre/bin/java
sudo alternatives --set libjavaplugin.so.x86_64 /usr/java/latest/jre/lib/amd64/libnpjp2.so
Pronto, agora testemos a execução de applets Java acessando a página "Verificar Versão do Java" e também os sites dos bancos brasileiros.
3.1.5. Utilizando o Fedy para instalar softwares proprietários
Particularmente, eu utilizo o Fedy para que a ele realize algumas configurações no Fedora e também a instale alguns softwares proprietários.
Note
|
Essas instalações/configurações são opcionais no contexto deste tutorial. |
Você pode obter a lista de configurações e instalações de softwares que o Fedy pode fazer através de sua interface gráfica. Alternativamente, pela linha de comando, você também pode obter esta listagem:
sudo fedy -e list
Para fazer minhas configurações e instalações através do Fedy eu executo o seguinte comando:
sudo fedy -e adobe_flash core_fonts dvd_playback essential_soft font_rendering google_chrome google_talkplugin media_codecs nautilus_dropbox rpmfusion_repos skype_linux teamviewer_linux
Note
|
Durante execução do comando acima, em meu caso, ocorreram erros na tentativa de instalação de |
3.2. Utilizando o Fedora numa box Vagrant
A forma mais simples de iniciar a execução desse tutorial talvez seja pela execução de uma máquina virtual rodando o Fedora. Isso pode ser realizado de maneira bem rápida pelo uso do VirtualBox e do Vagrant. Nesse caso, os passos sugeridos nesse tutorial são:
-
Baixe e instale o VirtualBox;
-
Baixe e instale o Vagrant;
-
Baixe e instale o Git;
Após ter instalado essas ferramentas, clone o projeto javaee-tutorial-testes
:
cd && git clone http://github.com/paulojeronimo/javaee-tutorial-testes
cd javaee-tutorial-testes
Antes de iniciar a box
Vagrant, instale o plugin vbguest
:
vagrant plugin install vagrant-vbguest
Inicie a box
com o comando up
do Vagrant. Isto fará o download da box
chef/fedora21
(caso ela ainda não esteja no cache ~/.vagrant.d
da tua máquina) e deverá demorar um pouco dependendo da velocidade da tua Internet:
vagrant up
4. Montando um ambiente para este tutorial
4.1. Baixando o JBoss EAP
O download do JBoss EAP exige um login
de usuário e, por isso, não é realizado de forma automática. Então, clique nos links abaixo para fazer esse download (se você não tiver uma conta em jboss.org será solicitado a criar):
Crie o diretório que conterá os arquivos baixados e copie-os para ele:
mkdir -p ~/javaee-tutorial-testes.backup/javaee-ambiente.instaladores
cp ~/Downloads/jboss-eap-6.3.0.* !$
4.2. Criando um ambiente para a construção, execução e testes de aplicações Java EE
Se você está utilizando a box
Vagrant desse tutorial, você já baixou o projeto javaee-tutorial-testes
. Do contrário, faça seu download através dos seguintes comandos:
cd && git clone http://github.com/paulojeronimo/javaee-tutorial-testes
cd javaee-tutorial-testes
Crie o arquivo config
a partir do config.exemplo
disponibilizado:
cp config.exemplo config
O arquivo config
contém informações sobre onde estão subprojetos utilizados neste tutorial. Se você fez forks desses projetos em tua conta no GitHub e fez adaptações neles, você poderá editar esse arquivo e ajustá-lo para apontar para teus forks.
Instale o ambiente.
Se estiver fazendo essa instalação numa máquina real, execute:
./instalar
Se estiver fazendo a instalação numa box
Vagrant (ela já deverá estar em execução), comande:
vagrant ssh -c /vagrant/instalar
O script instalar
criará o usuário javaee
(se a instalação estiver ocorrendo numa máquina real) e instalará um ambiente completo no $HOME
desse usuário para que você possa realizar as tarefas apresentadas neste documento. Assim que terminada a instalação, você precisará se tornar este usuário para executar quaisquer tarefas. Se a instalação estiver acontecendo numa box
Vagrant, esse script não criará o usuário javaee
: tudo estará no $HOME
do próprio usuário vagrant
.
O provisionamento da máquina (real ou da box
Vagrant) através do script instalar
deixará a máquina pronta com todos os requisitos necessários para o início das tarefas desse tutorial. No caso da box
, isso deverá demorar um pouco pois também será realizada a atualização dos pacotes instalados no Fedora. Esse script também fará o download de várias ferramentas que serão utilizadas neste tutorial.
Se você estiver executando o script instalar
na box
Vagrant, ele fará o reboot da máquina quando encerrar. Isso porque pode ter havido, durante a atualização dos pacotes, um update
do kernel. Nesse caso, serão necessários os seguintes comandos para que o VirtualBox Guest Additions volte a funcionar na box
:
vagrant vbguest --do install
vagrant reload
4.2.1. Observando a estrutura construída
Se você estiver executando o Fedora fora do Vagrant, logue-se como usuário javaee
através do seguinte comando:
sudo su - javaee
Se, ao contrário, você estiver executando o Fedora através do Vagrant, logue-se como usuário vagrant
através do seguinte comando:
vagrant ssh
Observe a estrutura de diretórios/arquivos montada no $HOME deste usuário:
tree -L 1
4.2.2. Iniciando e parando o JBoss
Para iniciar o JBoss, execute:
jboss_start
Para observar os logs do JBoss em execução, execute:
jboss_tail &
Note
|
Isso fará com que qualquer alteração no log do JBoss seja apresentada no shell corrente. Para encerrar esta apresentação, a qualquer momento, execute:
|
Para parar a execução do JBoss, execute:
jboss_stop
Para reinstalar o JBoss (em alguns exemplos faremos isto), execute:
jboss_instalar
4.2.3. Iniciando o Eclipse e instalando o JBoss Developer Studio
Se você estiver fazendo a instalação numa máquina real poderá instalar o Eclipse e o JBoss Developer Studio nela.
Note
|
Este tutorial ainda não foi ajustado para fazer uso de um ambiente gráfico numa |
Para iniciar o Eclipse, execute:
eclipse
Note
|
O comando |
Para instalar o JBoss Developer Studio, siga os passos descritos em na página do produto. Alternativamente, se ao invés de utilizar o procedimento de instalação descrito nesta página você desejar fazer a instalação offline, siga os passos descritos a seguir.
Baixe o zip com o update site do JBoss Developer Studio através do script a seguir:
jbdevstudio_baixar
Terminado o download, o arquivo baixado será salvo no diretório ~/instaladores
). Acesse a opção de menu Help > Install New Software… > Add… > Archive…
, selecione esse arquivo e prossiga com a instalação.
Para salvar o Eclipse configurado com os plugins que você instalou, encerre sua execução e execute:
eclipse_salvar
salvar_instaladores
5. Uma revisão, rápida e prática, sobre TDD e BDD
Talvez você queira dar uma olhada numa apresentação que fiz para a Capes, em 2013.
5.1. TDD sem JUnit, para os bravos
Leia o tutorial Test-Driven Development (TDD) em Java (Parte 1) mas, não execute-o.
Agora, você irá executá-lo de uma maneira ainda mais passo a passo e simples. Apenas brincando de copiar e colar os comandos, a seguir, num shell sendo executado pelo usuário javaee
. Dessa forma, você colocará TDD em prática e sem a preocupação de utilizar qualquer IDE.
Crie o diretório ~/exemplos/tdd
e vá para ele:
cd && mkdir -p exemplos/tdd
cd !$
Crie a classe MatematicaTest
:
cat > MatematicaTest.java <<EOF
public class MatematicaTest {
public void testFatorial() {
}
public void testFibonacci() {
}
public static void main(String args[]) {
MatematicaTest mt = new MatematicaTest();
try {
mt.testFatorial();
System.out.println("testFatorial() passou!");
mt.testFibonacci();
System.out.println("testFibonacci() passou!");
} catch (AssertionFailedError e) {
System.out.println("Teste falhou:");
e.printStackTrace();
} catch (Exception e) {
System.out.println("Teste provocou exceção:");
e.printStackTrace();
}
}
}
EOF
Compile o código e verifique que dá erro.
javac MatematicaTest.java
Conserte o erro, e recompile o código, criando a classe a seguir:
cat > AssertionFailedError.java <<EOF
public class AssertionFailedError extends Error {
public AssertionFailedError(String message) {
super(message);
}
}
EOF
!-2
Percebeu que você acabou de criar um mini framework
de testes (JUnit)!? =) Agora, comece a implementar os métodos de testes para, em seguida, criar a a implementação que fará estes testes passarem.
Modifique a classe MatematicaTest
implementando o método testFatorial
:
patch MatematicaTest.java << EOF
--- MatematicaTest.java.1 2015-02-08 18:15:02.007920683 -0200
+++ MatematicaTest.java 2015-02-08 18:27:09.016219866 -0200
@@ -1,10 +1,27 @@
public class MatematicaTest {
+ public static void fail(String message) {
+ throw new AssertionFailedError(message);
+ }
+
public void testFatorial() {
+ testFatorialComArgumentoNegativo();
+ //testFatorialDe0();
+ //testFatorialDe5a7();
}
public void testFibonacci() {
}
+ public void testFatorialComArgumentoNegativo() {
+ long result = -1;
+ try {
+ result = Matematica.fatorial(-1);
+ fail("fatorial(-1) deveria ter lançado IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // ok, isto era esperado!
+ }
+ }
+
public static void main(String args[]) {
MatematicaTest mt = new MatematicaTest();
try {
EOF
Observe as alterações, compile e verifique que dá erro pois, agora, será necessário criar a classe Matematica
que implementará o método fatorial.
vim MatematicaTest.java
Dentro do vim, pressione um Ctrl+Z para voltar ao shell e, em seguida, compile o código:
javac MatematicaTest.java
Note
|
A qualquer momento você pode retornar ao Vim, a partir do shell, executando o comando |
Crie a classe Matematica
, com uma implementação que fará o método de testes passar e, em seguida, recompile e reexecute a classes de testes:
cat > Matematica.java <<EOF
public class Matematica {
public static long fatorial(long n) {
if (n < 0)
throw new IllegalArgumentException();
return 0;
}
}
EOF
javac MatematicaTest.java
java MatematicaTest
Observe que o teste passou! \o/ Mas, ainda faltam vários testes e implementações a realizar até que você chegue ao código final. Siga em frente, criando um teste para validar o fatorial de 0. Em seguida, compile e reexecute. Você notará que sua implementação para a classe Matematica
precisará de mudanças em função do novo teste.
patch MatematicaTest.java <<EOF
--- MatematicaTest.java.2 2015-02-08 18:27:38.001992577 -0200
+++ MatematicaTest.java 2015-02-08 18:31:41.453083559 -0200
@@ -3,9 +3,17 @@
throw new AssertionFailedError(message);
}
+ public static void assertEquals(String message, long expected, long actual) {
+ if (expected != actual) {
+ throw new AssertionFailedError(message +
+ "\nValor esperado: " + expected +
+ "\nValor obtido: " + actual);
+ }
+ }
+
public void testFatorial() {
testFatorialComArgumentoNegativo();
- //testFatorialDe0();
+ testFatorialDe0();
//testFatorialDe5a7();
}
@@ -22,6 +30,10 @@
}
}
+ public void testFatorialDe0() {
+ assertEquals("fatorial(0) != 1", 1, Matematica.fatorial(0));
+ }
+
public static void main(String args[]) {
MatematicaTest mt = new MatematicaTest();
try {
EOF
javac MatematicaTest.java
java MatematicaTest
Este deverá ser o erro apresentado na execução do último comando:
Teste falhou: AssertionFailedError: fatorial(0) != 1 Valor esperado: 1 Valor obtido: 0 at MatematicaTest.assertEquals(MatematicaTest.java:8) at MatematicaTest.testFatorialDe0(MatematicaTest.java:34) at MatematicaTest.testFatorial(MatematicaTest.java:16) at MatematicaTest.main(MatematicaTest.java:40)
Para corrigí-lo, você deverá modificar a implementação do método fatorial
na classe Matematica
. Daí você poderá recompilar e fazer o teste passar novamente:
patch Matematica.java <<EOF
--- Matematica.java.1 2015-02-08 18:39:36.414359163 -0200
+++ Matematica.java 2015-02-08 18:41:59.534234153 -0200
@@ -2,6 +2,8 @@
public static long fatorial(long n) {
if (n < 0)
throw new IllegalArgumentException();
+ if (n == 0)
+ return 1;
return 0;
}
}
EOF
javac *.java
java MatematicaTest
Implemente o método de teste testFatorialDe5a7
na classe MatematicaTest
e, em seguida, faça o teste passar alterando, também, a classe Matematica
:
patch MatematicaTest.java <<EOF
--- MatematicaTest.java.3 2015-02-08 18:13:34.544606524 -0200
+++ MatematicaTest.java 2015-02-08 18:55:56.352636333 -0200
@@ -14,7 +14,7 @@
public void testFatorial() {
testFatorialComArgumentoNegativo();
testFatorialDe0();
- //testFatorialDe5a7();
+ testFatorialDe5a7();
}
public void testFibonacci() {
@@ -34,6 +34,16 @@
assertEquals("fatorial(0) != 1", 1, Matematica.fatorial(0));
}
+ public void testFatorialDe5a7() {
+ for (int i = 5; i <= 7; i++) {
+ switch (i) {
+ case 5: assertEquals("fatorial(5) != 120", 120, Matematica.fatorial(5)); break;
+ case 6: assertEquals("fatorial(6) != 720", 720, Matematica.fatorial(6)); break;
+ case 7: assertEquals("fatorial(7) != 5040", 5040, Matematica.fatorial(7)); break;
+ }
+ }
+ }
+
public static void main(String args[]) {
MatematicaTest mt = new MatematicaTest();
try {
EOF
patch Matematica.java <<EOF
--- Matematica.java.2 2015-02-08 18:57:08.081070792 -0200
+++ Matematica.java 2015-02-08 19:06:05.813831088 -0200
@@ -4,6 +4,12 @@
throw new IllegalArgumentException();
if (n == 0)
return 1;
+ else if (n == 5)
+ return 120;
+ else if (n == 6)
+ return 720;
+ else if (n == 7)
+ return 5040;
return 0;
}
}
EOF
javac *.java
java MatematicaTest
Enfim, implemente o método testFatorialDeN
na classe MatematicaTest
e execute-a:
patch MatematicaTest.java <<EOF
--- MatematicaTest.java.4 2015-02-09 01:58:00.285104599 -0200
+++ MatematicaTest.java 2015-02-09 02:04:24.212655227 -0200
@@ -1,3 +1,5 @@
+import java.util.Random;
+
public class MatematicaTest {
public static void fail(String message) {
throw new AssertionFailedError(message);
@@ -15,6 +17,7 @@
testFatorialComArgumentoNegativo();
testFatorialDe0();
testFatorialDe5a7();
+ testFatorialDeN();
}
public void testFibonacci() {
@@ -43,6 +46,31 @@
}
}
}
+
+ public void testFatorialDeN() {
+ long result;
+
+ // testa a regra "fatorial(n) = n * fatorial(n-1)" 30 vezes
+ // n é um número aleatório entre 0 e 20.
+ // Porque 20? Porque este é o inteiro máximo cujo fatorial
+ // não estrapola Long.MAX_VALUE: Veja em FatorialMaximo.java
+ Random r = new Random();
+ int n;
+ for (int i = 0; i < 30; i++) {
+ n = r.nextInt(20 + 1);
+ if (n < 0)
+ assert true : "n nunca deveria ser negativo!";
+ else {
+ result = Matematica.fatorial(n);
+ System.out.printf("%2d: Fatorial de %2d = %d\n", i, n, result);
+ if (n == 0)
+ assertEquals("fatorial(0) != 1", result, 1);
+ else
+ assertEquals("fatorial("+n+") != "+n+" * fatorial("+(n-1)+")",
+ result, n * Matematica.fatorial(n-1));
+ }
+ }
+ }
public static void main(String args[]) {
MatematicaTest mt = new MatematicaTest();
EOF
javac MatematicaTest.java
java MatematicaTest
Observe que, agora, seu programa de teste sempre irá falhar em algum momento. Não lhe restará outra alternativa a não ser fazer a implementação correta da classe Matematica
:
patch Matematica.java <<EOF
--- Matematica.java.3 2015-02-09 01:58:11.897021389 -0200
+++ Matematica.java 2015-02-09 02:14:33.710629599 -0200
@@ -2,14 +2,6 @@
public static long fatorial(long n) {
if (n < 0)
throw new IllegalArgumentException();
- if (n == 0)
- return 1;
- else if (n == 5)
- return 120;
- else if (n == 6)
- return 720;
- else if (n == 7)
- return 5040;
- return 0;
+ return n == 0 ? 1 : n * fatorial(n - 1);
}
}
EOF
Finalmente, seu programa de testes e sua implementação para a classe Matematica estarão corretos. Compile as classes e reexecute o programa de testes várias vezes para ter certeza disso:
javac *.java
for i in `seq 4`; do java MatematicaTest | (less; read n); done
Note
|
|
5.2. BDD, com Cucumber
Leia o artigo TDD e BDD em Aplicações Java EE com JUnit, Arquillian, Selenium e Cucumber, parte 1 mas, não execute-o.
Agora, vamos executá-lo utilizando o ambiente que montamos para o usuário javaee
:
Comece pela criação da feature
:
d=~/exemplos/bdd; rm -rf $d && mkdir -p $d && cd $d
d=src/test/resources/com/ladoservidor/cucumber/helloworld; mkdir -p $d
cat > $d/helloworld.feature <<'EOF'
Feature: Hello World
Scenario: Say hello
Given I have a hello app with "Hello"
When I ask it to say hi
Then it should answer with "Hello World"
EOF
Crie o pom.xml
do projeto:
cat > pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ladoservidor</groupId>
<artifactId>cucumber-jvm-helloworld</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>cucumber-jvm/HelloWorld</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.2</version>
<configuration>
<useFile>false</useFile>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
EOF
Observe a estrutura do projeto, até agora:
tree
Crie a classe RunCukesTest
que executará os testes do Cucumber através do JUnit:
d=src/test/java/com/ladoservidor/cucumber/helloworld; mkdir -p $d
cat > $d/RunCukesTest.java <<'EOF'
package com.ladoservidor.cucumber.helloworld;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@Cucumber.Options(
format = {
"pretty",
"html:target/cucumber-html-report",
"json-pretty:target/cucumber-json-report.json"
}
)
public class RunCukesTest {
}
EOF
Execute o maven:
mvn test
Observe a estrutura gerada para no diretório target
e abra o arquivo target/cucumber-html-report/index.html
:
tree target
browse target/cucumber-html-report/index.html
Crie a classe HelloStepdefs
:
cat > $d/HelloStepdefs.java <<'EOF'
package com.ladoservidor.cucumber.helloworld;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import static org.junit.Assert.assertEquals;
public class HelloStepdefs {
private Hello hello;
private String hi;
@Given("^I have a hello app with \"([^\"]*)\"$")
public void I_have_a_hello_app_with(String greeting) {
hello = new Hello(greeting);
}
@When("^I ask it to say hi$")
public void I_ask_it_to_say_hi() {
hi = hello.sayHi();
}
@Then("^it should answer with \"([^\"]*)\"$")
public void it_should_answer_with(String expectedHi) {
assertEquals(expectedHi, hi);
}
}
EOF
Crie a classe Hello
:
d=src/main/java/com/ladoservidor/cucumber/helloworld
mkdir -p $d
cat > $d/Hello.java <<'EOF'
package com.ladoservidor.cucumber.helloworld;
public class Hello {
private final String greeting;
public Hello(String greeting) {
this.greeting = greeting;
}
public String sayHi() {
return greeting + " World";
}
}
EOF
Reexecute os testes com o maven:
mvn test
Altere o arquivo helloworld.feature
para utilizar o português:
d=src/test/resources/com/ladoservidor/cucumber/helloworld
cat > $d/helloworld.feature <<'EOF'
# language: pt
Funcionalidade: Diga Olá
Cenário: Dizer "Olá Fulano!"
Dado que eu tenho uma app que recebe "Paulo"
Quando eu pedir que ela diga olá
Então ela deveria responder "Olá Paulo!"
EOF
Altere o RunCukesTest
para suportar o português:
patch -p1 <<'EOF'
--- ./src/test/java/com/ladoservidor/cucumber/helloworld/RunCukesTest.java 2013-04-05 15:44:14.000000000 -0300
+++ ../HelloWorld.pt/src/test/java/com/ladoservidor/cucumber/helloworld/RunCukesTest.java 2013-04-05 15:45:15.000000000 -0300
@@ -8,7 +8,8 @@ import org.junit.runner.RunWith;
format = {
"pretty",
"html:target/cucumber-html-report",
- "json-pretty:target/cucumber-json-report.json"
+ "json-pretty:target/cucumber-json-report.json",
+ "json:target/cucumber-pt.json"
}
)
public class RunCukesTest {
EOF
Altere o HelloStepsdefs
para suportar o português:
patch -p1 <<'EOF'
--- ./src/test/java/com/ladoservidor/cucumber/helloworld/HelloStepdefs.java 2013-04-05 15:44:14.000000000 -0300
+++ ../HelloWorld.pt/src/test/java/com/ladoservidor/cucumber/helloworld/HelloStepdefs.java 2013-04-05 15:45:15.000000000 -0300
@@ -1,8 +1,8 @@
package com.ladoservidor.cucumber.helloworld;
-import cucumber.api.java.en.Given;
-import cucumber.api.java.en.Then;
-import cucumber.api.java.en.When;
+import cucumber.api.java.pt.Dado;
+import cucumber.api.java.pt.Quando;
+import cucumber.api.java.pt.Entao;
import static org.junit.Assert.assertEquals;
@@ -10,17 +10,17 @@ public class HelloStepdefs {
private Hello hello;
private String hi;
- @Given("^I have a hello app with \"([^\"]*)\"$")
+ @Dado("^que eu tenho uma app que recebe \"([^\"]*)\"$")
public void I_have_a_hello_app_with(String greeting) {
hello = new Hello(greeting);
}
- @When("^I ask it to say hi$")
+ @Quando("^eu pedir que ela diga olá$")
public void I_ask_it_to_say_hi() {
hi = hello.sayHi();
}
- @Then("^it should answer with \"([^\"]*)\"$")
+ @Entao("^ela deveria responder \"([^\"]*)!\"$")
public void it_should_answer_with(String expectedHi) {
assertEquals(expectedHi, hi);
}
EOF
Altere o Hello
para português:
patch -p1 <<'EOF'
--- ./src/main/java/com/ladoservidor/cucumber/helloworld/Hello.java 2013-04-05 15:44:14.000000000 -0300
+++ ../HelloWorld.pt/src/main/java/com/ladoservidor/cucumber/helloworld/Hello.java 2013-04-05 15:45:15.000000000 -0300
@@ -8,6 +8,6 @@ public class Hello {
}
public String sayHi() {
- return greeting + " World";
+ return "Olá " + greeting;
}
}
EOF
Reexecute os testes:
mvn test
6. Testes "falsos", utilizando Mock Objects
7. Testes reais, utilizando o Arquillian
O Arquillian possibilita que executemos nossos testes dentro de um contêiner. Os testes são reais, o que significa dizer que não estamos testando uma interface "mockada" e, sim, uma implementação real dessa interface que pode estar sendo executada num servidor de aplicações.
Para iniciarmos o aprendizado de Arquillian, faremos uso dos exemplos disponibilizados no projeto JBoss EAP Quickstarts
.
8. O projeto JBoss EAP Quickstarts
8.1. Descompactando e configurando o Maven para testar os quickstarts
mkdir -p ~/exemplos
cd !$
unzip ~/instaladores/jboss-eap-6.3.0-quickstarts.zip
cd jboss-eap-6.3.0.GA-quickstarts/
cp settings.xml ~/.m2/
8.2. Executando e testando a aplicação helloworld
8.2.1. Iniciando e monitorando o log do JBoss
jboss_start
jboss_tail &
8.2.2. Implantando
cd helloworld
mvn clean install jboss-as:deploy
8.2.3. Verificando o funcionamento
Abra a URL http://localhost:8080/jboss-helloworld/.
8.2.4. Desimplantando
mvn jboss-as:undeploy
8.3. Executando e testando a aplicação tasks-rs
8.3.1. Iniciando o JBoss e implantando a aplicação
Se o JBoss não estiver em execução, execute:
jboss_start
Se o log do JBoss ainda não estiver sendo monitorado no shell corrente, execute:
jboss_tail &
Vá para o diretório da aplicação tasks-rs e crie um usuário para a aplicação. Execute os comandos abaixo:
cd ~/exemplos/jboss-eap-6.3.0.GA-quickstarts/tasks-rs
add-user.sh -a -u 'quickstartUser' -p 'quickstartPwd1!' -g 'guest'
mvn clean install jboss-as:deploy
O comando add-user
fará a adição de um usuário que será utilizado no processo de autenticação da aplicação tasks-rs
.
8.3.2. Executandoo a aplicação
Criando uma tarefa
curl -i -u 'quickstartUser:quickstartPwd1!' -H "Content-Length: 0" -X POST http://localhost:8080/jboss-tasks-rs/tasks/task1
Apresentando a tarefa no formato XML
curl -H "Accept: application/xml" -u 'quickstartUser:quickstartPwd1!' -X GET http://localhost:8080/jboss-tasks-rs/tasks/1
Apresentando todas as tarefa de um usuário
curl -H "Accept: application/xml" -u 'quickstartUser:quickstartPwd1!' -X GET http://localhost:8080/jboss-tasks-rs/tasks
Removendo uma tarefa
Para remover a tarefa associada ao id
1, execute:
curl -i -u 'quickstartUser:quickstartPwd1!' -X DELETE http://localhost:8080/jboss-tasks-rs/tasks/1
Liste as tarefas associadas ao quickstartUser
:
curl -u 'quickstartUser:quickstartPwd1!' -X GET http://localhost:8080/jboss-tasks-rs/tasks
Alterando o projeto para fazer a apresentação das tarefas no formato JSON
TODO
8.3.3. Testando a aplicação com o Arquillian
Testando numa instância JBoss já em execução
mvn clean test -Parq-jbossas-remote
Subindo uma instância JBoss apenas para executar os testes
jboss_stop
mvn clean test -Parq-jbossas-managed
8.3.4. Entendendo como os testes com o Arquillian são escritos
O pom.xml
As linhas geradas na execução do comando abaixo demonstram a configuração realizada no pom.xml
para a ativação do profile
arq-jbossas-managed
. Esse profile
, como vimos, é utilizado na execução do Maven para a realização de testes subindo uma nova instância do JBoss. Execute:
sed -n '186,200p' pom.xml
Esta é a saída gerada:
<profile>
<!-- An optional Arquillian testing profile that executes tests
in your JBoss EAP instance -->
<!-- This profile will start a new JBoss EAP instance, and execute
the test, shutting it down when done -->
<!-- Run with: mvn clean test -Parq-jbossas-managed -->
<id>arq-jbossas-managed</id>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-arquillian-container-managed</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
As linhas de 202 a 214 apresentam a definição do profile
arq-jbossas-remote
.
A estrutura de diretórios de teste
$ tree src/test/ src/test/ |-- java | `-- org | `-- jboss | `-- as | `-- quickstarts | `-- tasksrs | |-- DefaultDeployment.java | |-- TaskDaoTest.java | `-- UserDaoTest.java `-- resources |-- arquillian.xml |-- import.sql |-- META-INF | `-- test-persistence.xml `-- test-ds.xml 8 directories, 7 files
O arquivo arquillian.xml
Esse arquivo é utilizado para a realização de configurações no arquillian. Veja seu conteúdo:
vim src/test/resources/arquillian.xml
Os arquivos import.sql, test-persistence.xml e test-ds.xml
Quando o JBoss encontra o arquivo import.sql
no CLASSPATH (dentro de um pacote nesse caso) ele tenta, automaticamente, utilizar o seu conteúdo para popular a base de dados referenciada pelo Datasource configurado em test-ds.xml
. As configurações JPA para o ambiente de teste são realizadas no arquivo test-persistence.xml
.
Veja o conteúdo de cada um desses arquivos:
cat src/test/resources/import.sql
cat src/test/resources/test-ds.xml
cat src/test/resources/META-INF/test-persistence.xml
A classe DefaultDeployment
Essa classe contém métodos que serão utilizados pelo Arquillian (nos Testcases) que, através do ShrinkWrap, montaram o pacote com os recursos necessários para a execução dos testes.
As linhas geradas na saída do comando abaixo apresentam os principais métodos dessa classe. Execute:
sed -n '36,54p' src/test/java/org/jboss/as/quickstarts/tasksrs/DefaultDeployment.java
Esta é a saída gerada:
public DefaultDeployment() {
webArchive = ShrinkWrap.create(WebArchive.class, "test.war").addAsWebInfResource(
new File(WEBAPP_SRC, "WEB-INF/beans.xml"));
}
public DefaultDeployment withPersistence() {
webArchive = webArchive.addAsResource("META-INF/test-persistence.xml", "META-INF/persistence.xml").addAsWebInfResource(
"test-ds.xml", "test-ds.xml");
return this;
}
public DefaultDeployment withImportedData() {
webArchive = webArchive.addAsResource("import.sql");
return this;
}
public WebArchive getArchive() {
return webArchive;
}
Na classe TaskDaoTest: a anotação @RunWith
A anotação @RunWith(Arquillian.class)
é utilizada para informar ao JUnit que a execução da classe de teste será realizada através do Arquillian. Essa anotação é colocada no escopo da classe, conforme apresentado na saída do comando abaixo:
sed -n '41,42p' src/test/java/org/jboss/as/quickstarts/tasksrs/TaskDaoTest.java
@RunWith(Arquillian.class)
public class TaskDaoTest {
Na classe TaskDaoTest: a anotação @Deployment
A anotação @Deployment
é inserida num método estático para informar ao Arquillian o método responsável pela geração do pacote que será implantado para a realização dos testes. Antes da execução dos métodos de teste, o Arquillian se responsabiliza, através da execução desse método, de fazer o deploy de um pacote no contêiner, com todos os recursos necessários para a execução dos testes.
sed -n '44,48p' !$
@Deployment
public static WebArchive deployment() throws IllegalArgumentException, FileNotFoundException {
return new DefaultDeployment().withPersistence().withImportedData().getArchive()
.addClasses(Resources.class, User.class, UserDao.class, Task.class, TaskDao.class, TaskDaoImpl.class);
}
Na classe TaskDaoTest: a anotação @InSequence
A anotação @InSequence
é particular ao Arquillian e possibilita a ordenação do fluxo de execução dos métodos anotados com @Test
(anotação do JUnit) já que esse não oferece esta possibilidade. Então, através do uso dessa anotação podemos dizer ao JUnit a ordem em que desejamos a execução dos métodos de teste.
Na classe TaskDaoTest: os métodos de testes
Apesar de não utilizar nenhuma ferramenta para BDD, os métodos de teste na classe TaskDaoTest
são codificados seguem essa abordagem. No código é possível observamos comentários indicando o que seria o "given", o "when" e o "then". Além disso, os nomes dos métodos são bastante significativos. Observemos alguns:
Código de teste para "um usuário deveria ser criado com uma tarefa anexada":
sed -n '64,80p' !$
@Test
@InSequence(1)
public void user_should_be_created_with_one_task_attached() throws Exception {
// given
User user = new User("New user");
Task task = new Task("New task");
// when
em.persist(user);
taskDao.createTask(user, task);
List<Task> userTasks = em.createQuery("SELECT t FROM Task t WHERE t.owner = :owner", Task.class)
.setParameter("owner", user).getResultList();
// then
assertEquals(1, userTasks.size());
assertEquals(task, userTasks.get(0));
}
Código de teste para "uma faixa de tarefas deveria ser provida por taskDao":
sed -n '92,104p'!$
@Test
@InSequence(3)
public void range_of_tasks_should_be_provided_by_taskDao() {
// when
List<Task> headOfTasks = taskDao.getRange(detachedUser, 0, 1);
List<Task> tailOfTasks = taskDao.getRange(detachedUser, 1, 1);
// then
assertEquals(1, headOfTasks.size());
assertEquals(1, tailOfTasks.size());
assertTrue(headOfTasks.get(0).getTitle().contains("first"));
assertTrue(tailOfTasks.get(0).getTitle().contains("second"));
}
8.4. Executando e testando a aplicação kitchensink-angularjs
O projeto kitchensink-angularjs demonstra o uso das tecnologias AngularJS e JAX-RS e, além disso, apresenta um bom código para o estudo do Arquillian.
Ele é implantável com o Maven, utiliza Java EE 6 e as APIs CDI 1.0, EJB 3.1, JPA 2.0 e Bean Validation 1.0.
8.4.1. Baixando o projeto
cd ~/exemplos
git clone https://github.com/jboss-developer/jboss-wfk-quickstarts
cd jboss-wfk-quickstarts/kitchensink-angularjs
8.4.2. Construindo e implantando
mvn clean package jboss-as:deploy
8.4.3. Acessando a aplicação
8.4.4. Executando os testes com o Arquillian
mvn clean test -Parq-jbossas-remote
8.4.5. Executando testes funcionais com o Arquillian
8.4.6. Depurando a aplicação
8.4.7. Desimplantando
mvn jboss-as:undeploy
9. Utilizando JBoss Forge e Arquillian
-
Referências:
9.1. Criando uma aplicação Java EE simples
9.2. Inserindo segurança com o uso do Picketlink
9.3. Utilizando AngularJS no frontend
9.4. Testando JPA
9.5. Testando componentes EJB
9.6. Testando web services REST
9.7. Arquillian Drone e Graphene
10. Desenvolvendo uma aplicação do zero, utilizando TDD, Forge e Arquillian
11. Test-Driven JavaScript Development
12. Referências
-
Artigos e apresentações (antigas) produzidos pelo autor:
-
Projetos que tiveram a participação do autor:
-
Artigos:
-
Test Driven Development (Ref. básica)
-
TDD | Caelum (Ref. básica)
-
-
Livros:
13. Extras
13.1. Contribuindo com atualizações e/ou correções neste material
Este material é gratuito e publicado, livremente, no site http://paulojeronimo.github.io/javaee-tutorial-testes. Esse site e a sua impressão em formato pdf são gerados a partir de um código fonte escrito no formato Asciidoc através da ferramenta Asciidoctor. Um script bash (build) é utilizado para esta geração.
São bem vindas as contribuições (atualizações e/ou correções) a este material, que é disponibilizado sob uma licença Creative Commons. Essas contribuições podem ser submetidas via pull request no repositório do projeto.
Para instalar o Asciidoctor, execute:
gem install asciidoctor
Esse comando necessita de um ambiente Ruby instalado. Se você ainda não possui esse ambiente em teu Fedora, a melhor maneira de criá-lo é instalar o RVM para, através dele, instalar o Ruby. Isso pode ser feito com os seguintes comandos:
gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable
source ~/.profile
rvm install 2.2.0
ruby --version
Estando no diretório que contém o seu clone do projeto (javaee-tutorial-testes), para gerar o arquivo index.html e, em seguida, o arquivo javaee-tutorial-testes.pdf, execute os seguintes comandos:
./build
./build pdf