sábado, 13 de setembro de 2008

Novas atividades

Moçada,

sei que estou meio ausente, e voltar a ativa com um post que não tem nada haver com o blog é desastroso, mas tenho um bom motivo:

A mais de 2 meses venho estudando Ruby on Rails, direcionei todas as minhas atenções e horarios vagos para isso e estou percebendo que devo dedicar mais tempo ainda. Bem, o caso é que criei um novo blog pra falar sobre Ruby on Rails, e venho convidar a todos que se interessem pelo assunto a visita-lo. Bem, os assuntos que se relacionem com testes eu postarei nos 2 blogs, então se não se interessar por Rails, pode aguardar os posts sobre testes aqui mesmo.

Até mais

quinta-feira, 5 de junho de 2008

Fóruns que precisamos conhecer (Sobre Testes)

Em desenvolvimento, dúvidas são frequentes, por isso vou listar alguns lugares (Fóruns sobre teste e utilitários) que podemos encontrar soluções para os mais diversos problemas e mesmo encontrar respostas para nossas novas dúvidas.

EclEMMA: https://sourceforge.net/forum/forum.php?forum_id=614869
EMMA: http://sourceforge.net/forum/forum.php?forum_id=373865
JMock: http://www.jmock.org/mailing-lists.html
JMockit: https://jmockit.dev.java.net/servlets/SummarizeList?listName=users
JUnit: http://www.nabble.com/JUnit-f2687.html
Selenium: http://www.nabble.com/Selenium-f14083.html

XP-Rio (Sobre práticas ágeis, TDD, outras formas de testar software): http://tech.groups.yahoo.com/group/xprio/

Resolvendo o problema do finally no EclEMMA

A algum tempo reclamei do meu colega EclEMMA aqui, acontece que fiz uma pergunta num forum do produto, e mesmo com o meu inglês digno de um índio, obtive uma boa resposta:

"Hi, the yellow line is an artifact of the underlying coverage technology which is based on byte code instrumentation. "Finally" statements only exist in Java source code, the Java compiler creates corresponding control structured in byte code. The finally block is actually executed in two different cases: In case of normal execution and in case of an exception within the try block. You're test code follows only one the these paths, therefor the line is marked yellow (partial execution)."

Muito bacana, dessa forma percebi que tenho que deixar o método disparar a exceção, caso ela aconteça, e tenho que verifica-la no método de teste.

Usando o mesmo exemplo, eis a forma correta de testar e obter o resultado correto com o EclEMMA :

1: public class StupidClass {
2:
3: public String falar() throws Exception{
4:
5: try
6: {
7: System.out.println("try: ");
8: System.out.println("oyeah!");
9: }
10: catch (Exception e)
11: {
12: System.out.println("catch");
13: e.printStackTrace();
14: throw e;
15: }
16: finally
17: {
18: System.out.println("ogou!");
19: }
20:
21: return "oh! fezes";
22: }
23: }
e seu teste fica assim:

0: import static org.junit.Assert.assertEquals;
1: import static org.junit.Assert.assertNotNull;
2:
3: import org.junit.Before;
4: import org.junit.Test;
5:
6: public class TestStupidClass {
7:
8: StupidClass stupidClass = null;
9:
10: @Before
11: public void antes(){
12: stupidClass = new StupidClass();
13: }
14:
15: @Test
16: public void falarTest() throws Exception{
17:
18: assertEquals("Test 1", stupidClass.falar(), "oh! fezes");
19:
20: Exception exp = null;
21:
22: try{
23: stupidClass.falar();
24: }catch (Exception e) {
25: exp = e;
26: }
27:
28: assertNotNull("Test 2", exp);
29:
30: }
31: }
Obrigado ao pessoal do EclEMMA.

terça-feira, 13 de maio de 2008

O que o cliente quer, o que você entrega

Essa figura é bem manjada, mas ilustra muito bem o que acontece com o desenvolvimento de software. As metodologias ágeis vieram ao mundo pra tentar reduzir essa distancia entre o que o "cliente quer" e o que você entrega, algumas práticas como Interações Curtas e Testes podem te ajudar a estar sempre mais próximo de atingir os seus objetivos.

segunda-feira, 12 de maio de 2008

Teste de Recuperação

Depois de um email que me foi enviado pela Roberta Laquini, questionando alguns assuntos, procurei saber um pouco sobre Testes de Recuperação, bem, nunca ouvi falar e depois que vi uma fonte sobre o assunto percebi porque isso não é muito comum:

"Avalia o comportamento do sistema após ocorrência de erro, sobrecarga ou condições anormais, como queda de luz e rede. Nestes testes são tipicamente simulados cenários inesperados da mesma forma que ocorrem nos ambientes reais, como a remoção forçada de um cabo de rede, o desligamento repentino da energia, etc."

Imagina aí um cara desenvolvendo e indo debaixo da mesa puxar o cabo de força, mas é isso mesmo. :)

quarta-feira, 7 de maio de 2008

JMockit - RealMethodNotFoundForMockException

Existe um erro bem comum quando usamos JMockit: "Corresponding real methods not found for the following mocks".

Por um erro, meu, de tradução, imaginava que esse erro devia-se a um método da classe real não estar sendo criado na minha classe Mock, quando li com calma hoje pela manhã, percebi que é o contrário, um método implementado na minha classe Mock não existe na classe real.

Esse erro é comum de acontecer quando usamos uma classe de terceiro, normalmente empacotada num jar. No meu caso, tentava mockar a classe WebApplicationContextUtils do Spring e tive que procurar o código fonte no google pra saber a assinatura do método.

Percebi também que se você não criar o método falso para um método da classe real, ele vai invocar o método da classe real.

Problema com o CGLIB

O CGLIB foi descoberto quando procurávamos mockar classe concretas , ele é muito útil em muitas situações, principalmente quando usamos classes de outros. Acontece que esta semana descobrimos um probleminha nele: A classe que será mockada precisa do construtor default.

O interessante é que ele executa o construtor default quando o método mock(Class) é invocado. Isso pode ser um problema, no meu caso, a classe é de terceiro e não tem o construtor default, mas acredito que o CGLIB vai continuar sendo a alternativa pra esse tipo de situação.

segunda-feira, 5 de maio de 2008

Send us code. Get back tests. Free.

Dica de pesquisa.

O Igo deu uma dica de pesquisa bem interessante, como passei o dia tendo problemas com a rede e não consegui testar, envio a dica pra, caso alguém queira e tenha tempo, testar esse cara.

JUnitFactory promete gerar os testes para o seu código legado, com uma API de Mock Objects diferente das mais populares de mercado, pode ser uma boa alternativa pra quem tem interesse em gerar grande quantidade de testes para código já existente.

Aqui uma demonstração de como usar o JUnitFactory.

quinta-feira, 24 de abril de 2008

EclEMMA e seus problemas com o finally

Hoje tive a maior decepção do ano, logo com o EclEMMA, um plugin que tinha como extremamente confiável.

Para ilustrar o problema vou fazer um exemplo bem simples (desculpem os termos usados, mas estou meio irritado com isso):
0:
1: public class StupidClass {
2:
3: public String falar(){
4:
5: try
6: {
7: System.out.println("oyeah!");
8: }
9: finally
10: {
11: System.out.println("ogou!");
12: }
13:
14: return "oh! fezes";
15: }
16:
17: }
E seu teste:
0: import static org.junit.Assert.assertEquals;
1:
2: import org.junit.Before;
3: import org.junit.Test;
4:
5: public class TestStupidClass {
6:
7: StupidClass stupidClass = null;
8:
9: @Before
10: public void antes(){
11: stupidClass = new StupidClass();
12: }
13:
14: @Test
15: public void blablabla(){
16: assertEquals(stupidClass.falar(), "oh! fezes");
17: }
18:
19: }
Para a minha infeliz surpresa, o corpo do finally não é considerado coberto, na verdade ele é "amarelado".
Pra quem não sabe o que significa o amarelo ou verde ou vermelho, veja aqui.

Agora é buscar outra ferramenta que atenda essa necessidade, quando, e se, achar, posto aqui.

quinta-feira, 17 de abril de 2008

Acessando um método não visível "na marra"

Um dia eu falei que um método privado deveria ser testado por intermédio de outro, já que se não existir um método que o chame, ele nem deve existir. Bem, hoje vi que não é verdade, ou pelo menos encontrei um caso que precisava implementar um método protected em uma classe que seria enviada como parâmetro para outro método.

Aí pensei, "tá na hora de sujar as mãos".

Como é possível que nem todos saibam usar Reflection, vou postar aqui o código para ajudar a quem precisar:

Este é o método:

protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication(mailSender.getUsername(), mailSender.getPassword());
}

e assim o acesso:

public void testGetPasswordAuthentication() throws Exception{
Method metodo = authenticatorImpl.getClass().getDeclaredMethod( "getPasswordAuthentication", new Class[]{});
metodo.setAccessible(true);
metodo.invoke(authenticatorImpl, new Object[]{});
}

Boas práticas - Parte II

Uma boa prática que tenho percebido ajudar muito na análise de testes, é o uso do parâmetro (message) do tipo String que todos os asserts do JUnit suportam.
Desta forma indico que sempre este parâmetro seja utilizado.

Exemplos:
assertEquals("Test 1", true, true);
assertTrue("Test 2", false);
assertNotNull("Test 3", new Object());

e todos os outros asserts se comportam da mesma forma.

O erro aparecerá assim: junit.framework.AssertionFailedError: Test 2
Indicando onde o erro aconteceu.

Numa classe de testes onde você tenha muitos assert é quase impossível, ou no mínimo complicado, encontrar em que assert o teste falhou.

quarta-feira, 2 de abril de 2008

BDD - Mais uma técnica de programação?

Behaviour Driven Development é algo que, atualmente, se ouve bastante no meio Ágil, e lendo um artigo do Ivan Sanchez sobre o assunto, vejo que não existem diferenças "em termos de técnica de programação" entre BDD e TDD (Test Driven Development), aparentemente o BDD foi criado para substituir conceitualmente o TDD, pois o termo Test pode ser (e é) facilmente confundindo com Validação e causa uma certa distância entre nós e os humanos normais (não-desenvolvedores).

Segundo o Portal do BDD, esta técnica é a evolução "lógica" do TDD e do Acceptance Test Driven Planning. BDD têm um vocabulário que pode ser utilizado por todos os interessados: empresa, desenvolvedores, testadores, analistas e gerentes. Este vocabulário é tido como o "grande bum" do BDD.

BDD é mais rico que TDD quando pensamos em ferramentas, pois existe um conjunto de frameworks que auxilia na sua implementação, para Java o mais famoso (e o único que eu conheço) é o JBehave.

Vejo BDD como uma idéia bem bacana e que merece ser experimentada, os depoimentos que escuto são sempre positivos.

Desacoplando para melhorar o design e facilitar os testes

Usando TDD, normalmente nos deparamos com situações em que precisamos mudar nossa implementação para atender a algum teste, isso é normal e viável, já que os testes são parte do sistema.

Quando o Paulo Silveira veio a Fortaleza e ministrou um workshop sobre Java, falou-se muito sobre desacoplar, não usar métodos estáticos (sempre que possível) e outras boas práticas como IoC, acontece que venho buscando sempre soluções pra esses casos sem mudar minha implementação, na maioria até consegui, lógico que com maior dificuldade e esforço.

Assim, esta semana discutimos a implementação de alguns testes que estavam muito complicados e ví que o que o Paulo havia falado era bem simples, ao menos da maneira que solucionamos o problema. Vou ilustrar com um exemplo bem simples pra deixar claro como podemos facilitar os testes desacoplando algumas coisas.

Primeiro quero dizer que com um Spring da vida você não vai precisar usar esse artifício, pois com o IoC do Spring você pode fazer isso;
Segundo, é importante ficar atento, porque se existirem muitas dependência, um artifício simples como esse pode não funcionar.

Ao exemplo:

Tenho 3 classes.
0: public class ClasseA {
1: public void metodo1(){
2: ClasseB classeB = new ClasseB();
3: classeB.metodo2();
4: }
5: }
0: public class ClasseB {
1: public void metodo2(){
2: System.out.println(ClasseUtil.m1());
3:
4: ClasseUtil classeUtil = new ClasseUtil();
5: System.out.println(classeUtil.m2());
6: }
7: }
0: public class ClasseUtil {
1: public static String m1(){
2: return "m1";
3: }
4:
5: public String m2(){
6: return "m2";
7: }
8: }
No teste da ClassseB terei algumas dificuldades porque existe uma chamada a um método estático (podemos usar JMockit, porém, dependendo do retorno, torna-se uma tarefa muito complicada) e um instanciamento da ClasseUtil.

Como poderíamos evitar isso? Bem simples:

Passaremos a instância da ClasseUtil como parâmetro do método:
0: public class ClasseA {
1: public void metodo1(){
2: ClasseB classeB = new ClasseB();
3: classeB.metodo2(new ClasseUtil());
4: }
5: }

0: public class ClasseB {
1: public void metodo2(ClasseUtil classeUtil){
2: System.out.println(classeUtil.m1());
3: System.out.println(classeUtil.m2());
4: }
5: }

O design fica bem simples e os testes mais fáceis de implementar.

segunda-feira, 17 de março de 2008

Devo mockar métodos de classes "utilitárias"?

Se considerarmos que esses métodos estão fora do escopo testado ou que serão testados isoladamente, a resposta é não, porém, pensando com cuidado, percebo que esses métodos normalmente são recheados de Reflections e Generics para serem o mais genérico possível, e pensando em algumas situações acho que podemos mudar de opinião.

Temos aqui na empresa uma classe CollectionUtil onde criamos métodos para manipular Collections. Nesta classe criamos um método não tão genérico quanto o possível, porém, o suficiente para a nossa utilização, sendo que me deparei com uma coisa:

O método é responsável por receber uma Collection e retornar um Array de ids (todas as nossas entidades tem esse atributo):

 0: public Long[] convertCollectionToArrayIds(
1: Collection coll)
2: {
3: Method mKey = null;
4: Long[] ids = new Long[coll.size()];
5:
6: try
7: {
8: int count = 0;
9: for(T clazz: coll)
10: {
11: mKey = clazz.getClass().
12: getMethod("getId");
13: ids[count] = (Long) mKey.invoke(
14: clazz,new Object[]{});
15: count++;
16: }
17:
18: return ids;
19: }
20: catch(Exception e)
21: {
22: e.printStackTrace();
23: return null;
24: }
25: }

Imagine uma situação onde alguém, não importa o porque, não use o atributo "id" e sim "cod" ou um erro de digitação e sai um "ide", ou qualquer outra coisa, pensando nisso, vejo que a melhor saída é: Não mockar o método "utilitário".

Use o bom senso, um método utilitário que envie um email ou que não seja tão genérico assim, pode ser mockado. Outra alternativa é enriquecer o método "utilitário", fazendo verificações se a entidade possui o atributo "id".

terça-feira, 11 de março de 2008

Mais sobre Cobertura de Código (Code Coverage)

Mais uma vez caí numa discussão sobre garantir ou não 100% de cobertura, na ultima vez que discuti o assunto caí num ponto onde 100% de Cobertura só serve para o marketing da minha empresa, ou setor ou de um produto, de qualquer forma, somente para Propaganda. Acontece que algumas coisas foram mudando minha opinião, e achei que deveria atualizar a opinião do Blog também.

* Sem garantir os 100% de Cobertura, fica complicado verificar se o que deve ser testado, foi testado. O responsável, ou toda a equipe tem que ficar constantemente verificando a cobertura de uma determinada classe ou de um determinado pacote;
* Alterações em um método que não deveria ser testado fazem com que ele precise de teses. Num "getzinho" simples que só retorna uma data, você pode incluir um regra que: se não houver data no objeto, retorne a data de hoje. Se o desenvolvedor esquecer de fazer o teste, dificilmente alguém vai perceber a ausência dele;
* Num momento de refactoring, a confiança nos testes fica abalada. Se você não sabe se todo o sistema esta coberto, como você vai garantir que suas alterações não quebraram o código? Resposta fácil, procurando a classe de testes responsável pelo método que esta sendo refatorado e verificando se existe algum teste para aquele método específico.

Quando pensamos em todos esses pontos, e percebemos outros como o que vi no Blog do Wagner Elias, ótimo texto por sinal, percebemos que garantir a cobertura de código é muito válido, não somente a nível de Propaganda.

Uma alternativa muito boa é automatizar alguns testes, ou gerar o código das situações mais genéricas, prefira sempre a primeira situação, manter código desnecessário é um custo muito alto. Sendo assim, um get ou set estaria sendo coberto por esse teste genérico, até ter sua implementação alterada, neste caso um teste "de verdade" seria criado para ele.

Para ver os outros post sobre o assunto.

quinta-feira, 6 de março de 2008

Método mockado retornando uma Exception

Recentemente precisei fazer um teste em um método e que deveria verificar o comportamento do mesmo quando um método invocado por ele retornasse uma Exception específica.

Exemplo do método:
0: public String delete() throws Exception
1: {
2: try
3: {
4: vendaManager.delete(venda.getId());
5: }
6: catch (UnexpectedRollbackException e)
7: {
8: addActionError("Não foi possível cancelar venda.");
9: return Action.INPUT;
10: }
11: return Action.SUCCESS;
12: }
No expect do método é só retornar a exception da seguinte forma:
0: vendaManager.expects(once()).method("delete")
1: .with(ANYTHING).will(throwException(
2: new UnexpectedRollbackException("")));
3: assertEquals(action.delete(), "input");

quinta-feira, 28 de fevereiro de 2008

Descobrindo o Selenium

Selenium é a ferramenta mais utilizada para Testes de Integração, ou ao menos a mais popular no meio Java.

Para os nossos exemplos, vamos usar o plugin para o FireFox que facilita muito a tarefa (Uma lista de links importantes esta no final do post, incluindo downloads de plugins e ferramentas).

O Selenium tem o seu próprio testRunner, porém nós vamos utilizar o Selenium-RC para testarmos via código Java, isso possibilita integrar os testes de Selenium com seus Testes Unitários.

Com o plugin do FireFox, iniciamos a usar o Selenium:



Começamos a gravar o processo que queremos testar, no caso um simples login:



Quando completamos o processo que queremos testar, paramos a gravação:



Exportamos o teste feito para código Java:



O código gerado é esse:

0: import com.thoughtworks.selenium.*;
1: import java.util.regex.Pattern;
2:
3: public class login extends SeleneseTestCase {
4: public void testLogin() throws Exception {
5: selenium.open("/login.action;" +
6: "jsessionid=21FC72563" +
7: "78F1D482601E23C929635A1");
8: selenium.type("j_password", "1234");
9: selenium.click("submit");
10: selenium.waitForPageToLoad("30000");
11: assertEquals("Área Inacessível",
12: selenium.getAlert());
13: }
14: }

O objeto selenium no método acima, nos da informações da página, como um alert, tempo esperado para a página carregar, o titulo, dentre outras coisas.

Para que o teste fique completo, estendemos TestCase, declaramos um atributo selenium do tipo Selenium e criamos o setUp e tearDown:

0: public void setUp() throws Exception {
1: String url =
2: "http://www.meuslivroseletronicos.com.br";
3: selenium = new DefaultSelenium("localhost",
4: SeleniumServer.getDefaultPort(),
5: "*firefox", url);
6: selenium.start();
7: }
8:
9: protected void tearDown() throws Exception {
10: selenium.stop();
11: }

A classe finalmente deve ficar assim:

0: import junit.framework.TestCase;
1: import org.openqa.selenium.server.SeleniumServer;
2: import com.thoughtworks.selenium.DefaultSelenium;
3: import com.thoughtworks.selenium.Selenium;
4:
5: public class login extends TestCase {
6: private Selenium selenium;
7: public void setUp() throws Exception {
8: String url = "http://www.meuslivroseletronicos" +
9: ".com.br";
10: selenium = new DefaultSelenium("localhost",
11: SeleniumServer.getDefaultPort(), "*firefox",
12: url);
13: selenium.start();
14: }
15:
16: protected void tearDown() throws Exception {
17: selenium.stop();
18: }
19:
20: public void testLoginLivro() throws Exception {
21: selenium.open("/login.action;jsessionid=BEF" +
22: "47FEE6B27A5E7EA20DB20E6F3F4F2");
23: selenium.type("j_username", "admin");
24: selenium.type("j_password", "1234");
25: selenium.click("submit");
26: selenium.waitForPageToLoad("30000");
27: assertEquals("Livro Eletronico",
28: selenium.getTitle());
29: }
30: }

Estarte o selenium server: java -jar selenium-server.jar que está no diretório selenium-remote-control-0.9.2\selenium-server-0.9.2 do Selenium-RC.

Agora rode como se fosse um teste comum do JUnit.



Links que podem ajudar:

Selenium (http://selenium-ide.openqa.org/)
Download em (http://selenium-core.openqa.org/download.jsp)

Selenium IDE 4 Firefox (http://release.openqa.org/selenium-ide/0.8.7/selenium-ide-0.8.7.xpi)

Selenium 4 Java (http://selenium-rc.openqa.org/java.html)
Download em (http://selenium-rc.openqa.org/download.jsp)

segunda-feira, 25 de fevereiro de 2008

Teste de Integração

O Teste de Integração, ao contrario do Teste Unitário, não se preocupa com o conceito de módulos. Essa modalidade de testes, como seu nome indica, promove o teste de uma funcionalidade sem se preocupar com "de quem é a responsabilidade?".

Pense numa situação de um cadastro, com Teste Unitário você deve criar um teste para as regras de negócios, um outro para os métodos de persistência, porém, no Teste de Integração, você testa somente o método Inserir, ou Update ou Delete e deixa que os módulos si relacionem de verdade, sem Mock Objects, sem nenhum artifício qualquer.

Quando falo "comparando" Testes Unitários com Testes de Integração, não estou dizendo que um é melhor que o outro, até porque eles devem conviver de maneira harmônica, os Testes de Integração verificam um outro escopo, verificam a integração entre os módulos, tanto que, se os testes unitários foram feitos de maneira correta, um teste de integração NUNCA (ao menos, não deverá) encontrará um problema na regra do método implementado, pode encontrar problemas numa injeção de dependências feita pelo Spring ou coisa parecida.

Breve vou escrever sobre o Selenium, uma ferramenta espetacular para fazer Testes de Integração.

segunda-feira, 18 de fevereiro de 2008

Melhorando o Design da Aplicação e a construção de testes

O TDD tem como sua maior incumbência, auxiliar na construção do design dos seus métodos, com o tempo vimos situações particulares e tentamos encontrar soluções para as mais diversas situações, como JMockit, CGLib e outros.

Porém tivemos, aqui na empresa, a visita do Paulo Silveira da CAELUM e mudou bastante o meu conceito de como alcançar a melhoria no desenvolvimento de testes e consequentemente, das classes de um sistema.

Citarei umas idéias que achei bem interessantes:

1) Use Injeção de Dependência. No Spring temos o IoC que nos proporciona essa técnica, é notório a facilidade que ganhamos na construção de testes, principalmente no TDD.
2) Use e abuse de Interfaces. O uso de Interfaces nos oferece, além do óbvio, uma facilidade muito maior na construção de testes, pois o padrão para construção de Mock Objects é utilizar Interfaces.
3) Evite os método estáticos. Métodos estáticos tem alto acoplamento e são ruins de testar, neste momento eu falei sobre JMockit, mas ele frisou que testes devem ser simples e que artifícios como esses podem complicá-los.

Por fim, ele falou algo que achei bem interessante, acho que ele tirou do Design Patters do GoF:

"Evite as extenções e abuse das implementações", não sei bem se foram essas as palavras, mas a idéia foi essa.

E como me lembrou o Capitão Sávio Pierre, "Em vez de extender, procure fazer Agregações ou Composições". Isso também é sensacional.

Bem, acho que existem situações em que essas boas práticas podem não ser possíveis, mas confesso que estarei sempre atento a elas.

Fica aqui a indicação da CAELUM e do próprio Paulo Silveira, o workshop ministrado por ele foi muito bacana.

segunda-feira, 11 de fevereiro de 2008

Fazendo testes com Ant + JUnit + JMockit

Para fazer seus testes com o auxílio do Ant, seguem alguns passos que extraí do que já utilizamos aqui na empresa:

Primeiro Passo: crie 2 arquivos e coloque na raiz da aplicação:

build.xml
build.properties


Segundo Passo: no build.xml, crie a seguinte estrutura (ela pode ser mais simplificada):

*tire os '$' de todas as linhas
<$project name="NomeDoProjeto" basedir=".." default="integrate">
<$property file="${ant.project.name}/build.properties"/>
<$property name="project.dir" value="${ant.project.name}"/>
<$property name="src.dir" value="${project.dir}/src"/>
<$property name="build.dir" value="${project.dir}/web/WEB-INF/classes"/>
<$property name="lib.dir" value="${project.dir}/web/WEB-INF/lib"/>
<$property name="allTests.class" value="com.NomeDoProjeto.test.AllTests"/>

<$path id="project.classpath">
<$pathelement location="${build.dir}"/>
<$fileset dir="${lib.dir}">
<$include name="*.jar"/>
<$/fileset>
<$fileset dir="${tomcat.lib}">
<$include name="*-api.jar"/>
<$include name="jasper-runtime.jar"/>
<$/fileset>
<$/path>

<$taskdef name="junit" classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask">
<$classpath refid="project.classpath"/>
<$/taskdef>

<$target name="build">
<$mkdir dir="${build.dir}"/>
<$javac debug="on" srcdir="${src.dir}" destdir="${build.dir}" failonerror="true">
<$classpath refid="project.classpath"/>
<$/javac>
<$/target>

<$target name="test" depends="build">
<$dirname file="build.xml" property="current.dir"/>
<$junit haltonfailure="true" haltonerror="true" fork="true" dir="${current.dir}">
<$jvmarg value="-javaagent:${jmockit.dir}"/>
<$classpath>
<$path refid="project.classpath"/>
<$/classpath>
<$formatter type="plain" usefile="false"/>
<$test name="${allTests.class}"/>
<$/junit>
<$/target>

<$/project>

Terceiro Passo: no build.properties, crie as variáveis( ${variavel} ) que são usadas no xml:

tomcat.lib=C:\\Arquivos de programas\\Apache Software Foundation\\Tomcat 5.5\\common\\lib
jmockit.dir=C:\\jmockit.jar

Quarto Passo: Para rodar o Ant (estou usando o Eclipse), clique com o botão direito no build.xml / Run As / Ant Build...

Na aba Targets, marque a target test e na aba Classpath, adicione o jar do JUnit.



A partir daí é só rodar.


Para o uso do JMockit adicionamos estas linhas (estão adicionadas nos exemplos acima, as linhas seguintes são somente informativas, porém se você não usar JMockit, remova-as):

No build.xml:
*sem o '$'
<$jvmarg value="-javaagent:${jmockit.dir}"/>
Adiciona o argumento na JVM.

No build.properties:
jmockit.dir=C:\\jmockit.jar
Indica onde está o jar do JMockit.

Problema com JMockit

O JMockit foi comentado no post Mockando um Método Estático.

Bem, em algumas maquinas aqui da empresa, estava acontecendo o seguinte erro:

java.lang.VerifyError: (class: junit/framework/TestCase, method: runTest signature: ()V) Illegal constant pool index
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2365)
at java.lang.Class.getMethod0(Class.java:2611)
at java.lang.Class.getMethod(Class.java:1579)at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestLoader.
getTest(JUnit3TestLoader.java:99)at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestLoader.
loadTests(JUnit3TestLoader.java:59)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.
runTests(RemoteTestRunner.java:445)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.
runTests(RemoteTestRunner.java:673)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.
run(RemoteTestRunner.java:386)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.
main(RemoteTestRunner.java:196)



O interessante esta no trecho "em algumas maquinas", se o jar do JMockit e o método onde foi usado são os mesmos, por que somente em algumas maquinas aconteceu esse erro, e não em todas?

A verdade é que o erro não tem nada haver com JMockit, mas como faz parte do processo de fazer o JMockit funcionar, vou postar a minha solução aqui:

Li em vários lugares que o problema era causado pela JVM, que não suportava o parâmetro (figura abaixo). Onde li, dizia que o motivo era a quantidade de parâmetros passados pra JVM, e que era um problema da VM do Java 5. Não me aprofundei no assunto e nem pretendo.



O Runtime JRE, estava apontando para o diretório de uma JRE instalada, então apontei para o diretório da JDK e pronto, funcionou:



Caso este procedimento não seja suficiente para fazer com que seus testes funcionem, o problema pode ser da versão da sua JVM (a testada foi 1.5.0_08), instale outra e refaça o procedimento.

quinta-feira, 7 de fevereiro de 2008

Confusões sobre Cobertura de Código

Existem muitas idéias confusas sobre o conceito "100% de Cobertura", além das que já existentes no sentido de seguir ou não esta prática.

Começo esclarecendo que a Cobertura de Código é baseada em código coberto por Testes Unitários, pois um teste de Integração, por exemplo, testa telas, integração entre módulos, mas não analisa linhas cobertas.

Outra confusão é sobre a profundidade de um teste, quando tratamos deste assunto. Para que um método esteja 100% Coberto, o teste precisa somente passar por todas as suas linhas do código do método testado. Desta forma, o teste pode não ser completo, pois não testará "todas" as possibilidades, e mesmo assim será considerado 100% coberto. Com isso, não quero dizer que não devemos procurar a completude do teste.

A cobertura de código não garante, por si só, a qualidade no desenvolvimento, ter seu código 100% coberto é somente uma das várias práticas que lhe ajudam a obter sucesso no desenvolvimento.

Para garantir 100% de Cobertura, algumas vezes você será obrigado a fazer testes "inúteis", tomando o exemplo de meros getters e setters, esse é um dos motivos que faz com que algumas pessoas não "gostem" da idéia.

Manter seu código 100% Coberto é uma tarefa muito questionada, em várias situações quando nos preocupamos apenas com a arquitetura da nossa aplicação, preferimos não manter o código 100% Coberto. Porém, essa decisão tem efeitos comerciais, e estes também devem ser considerados.

quinta-feira, 24 de janeiro de 2008

Teste Unitário - Como definir o seu escopo?

Quando se está começando a fazer testes, o assunto mais incompreendido é "até onde o teste unitário deve testar". Muitos confundem a idéia de unidade, e até começar a usar Mock Objects é um passo gigantesco e confuso.

Definições encontradas na WEB:

"O teste unitário ou de unidade é uma modalidade de testes que se concentra na verificação da menor unidade do projeto de software."
"Este estágio tem foco na verificação dos menores elementos testáveis do software."
"O teste unitário se limita a testar uma única unidade isolada."

Todas estão certas, e a minha definição para essa categoria de testes é: "Um teste que não ultrapassa a responsabilidade do método testado".

O que quer dizer que, não podemos ultrapassar a responsabilidade do método, da classe ou da camada. O negócio exige bom senso, pois um exemplo de quando ultrapassar a barreira do método é no caso de testar Métodos Privados, porém os escopos se confundem nesses casos.

Situações mais claras são quando tratamos classes diferentes, por exemplo:

Temos uma classe (ClassePeba) e testamos um método (getAlgo) desta classe:

 0: public class ClassePeba {
1: public String getAlgo(int i){
2: String palavra = "blablabla";
3: ClasseCriptografa classeCriptografa =
4: new ClasseCriptografa();
5: palavra = classeCriptografa.criptogravaPalavra(
6: palavra, i);
7: return palavra;
8: }
9: }
o método (getAlgo) invoca um outro método (criptogravaPalavra) de outra classe (ClasseCriptografa):

 0: public class ClasseCriptografa {
1: public String criptogravaPalavra(String palavra, int i) {
2: if(i == 1)
3: {
4: palavra = palavra.replace("a", "@");
5: palavra = palavra.replace("b", "!");
6: palavra = palavra.replace("l", "?");
7: }else{
8: palavra = palavra.replace("a", "*");
9: palavra = palavra.replace("b", "$");
10: palavra = palavra.replace("l", "%");
11: }
12:
13: return palavra;
14: }
15: }

No teste do método (getAlgo):

 0: import org.junit.Before;
1: import org.junit.Test;
2: import static org.junit.Assert.*;
3:
4: public class TestClassePeba {
5:
6: ClassePeba classePeba;
7:
8: @Before
9: public void prepare()
10: {
11: classePeba = new ClassePeba();
12: }
13:
14: @Test
15: public void testGetAlgo()
16: {
17: assertEquals("!?@!?@!?@", classePeba.getAlgo(1));
18: assertEquals("$%*$%*$%*", classePeba.getAlgo(2));
19: }
20: }

Estamos testando duas classes distintas, caso aconteça uma inconformidade na outra classe (ClasseCriptografa), o erro irá acontecer no teste do método getAlgo, que não tem erro algum.

Dessa forma, o que deve ser feito é:

Primeiro, faço uma Refatoração para não instanciar a classe dentro do método, o teste definiu o design do método:
0: public String getAlgo(int i,
1: ClasseCriptografa classeCriptografa){
2:
3: String palavra = "blablabla";
4: palavra = classeCriptografa.criptogravaPalavra(
5: palavra, i);
6: return palavra;
7: }
Crie um teste para o método getAlgo, mockando a classe ClasseCriptografa:

* para mockar uma classe com JMock, é necessário** criar a interface:
0: public interface ClasseCriptografiaInterface {
1: public abstract String criptogravaPalavra(
2: String palavra, int i);
3: }
** tambem é possível mockar uma classe concreta, veja aqui.

 0: import org.jmock.Mock;
1: import org.jmock.MockObjectTestCase;
2: import org.junit.Before;
3: import org.junit.Test;
4:
5: public class TestClassePeba extends MockObjectTestCase{
6:
7: ClassePeba classePeba;
8: Mock classeCriptografa = null;
9:
10: @Before
11: public void setUp()
12: {
13: classePeba = new ClassePeba();
14: classeCriptografa = new Mock(
15: ClasseCriptografiaInterface.class);
16: }
17:
18: @Test
19: public void testGetAlgo()
20: {
21: classeCriptografa.expects(once()).
22: method("criptogravaPalavra").
23: with(eq("blablabla"), eq(1)).will(returnValue(
24: "!?@!?@!?@"));
25: assertEquals("!?@!?@!?@", classePeba.getAlgo(1, (
26: ClasseCriptografiaInterface)classeCriptografa.
27: proxy()));
28:
29: classeCriptografa.expects(once()).method(
30: "criptogravaPalavra").with(eq("blablabla"),
31: eq(2)).will(returnValue("$%*$%*$%*"));
32: assertEquals("$%*$%*$%*", classePeba.getAlgo(2,
33: (ClasseCriptografiaInterface)classeCriptografa.
34: proxy()));
35: }
36: }

Até aqui o método criptogravaPalavra não foi sequer executado.

Depois criamos um teste específico para a classe ClasseCriptografa:

 0: import static org.junit.Assert.assertEquals;
1:
2: import org.junit.Before;
3: import org.junit.Test;
4:
5: public class TestClasseCriptografa{
6:
7: ClasseCriptografa classeCriptografa;
8:
9: @Before
10: public void setUp()
11: {
12: classeCriptografa = new ClasseCriptografa();
13: }
14:
15: @Test
16: public void testGetAlgo()
17: {
18: assertEquals("!?@!?@!?@", classeCriptografa.
19: criptogravaPalavra("blablabla",1));
20: assertEquals("$%*$%*$%*", classeCriptografa.
21: criptogravaPalavra("blablabla",2));
22: }
23: }

Pronto!

A idéia é ter seus testes bem separados, pois em momentos de refatoração, encontrar os erros do método e a alteração do próprio teste será facilitada.