Problema ao executar classe JUnit em projeto Web

Voltei. Mas para perguntar, não responder. :slight_smile:

Explicá-los-ei. Preciso executar um teste JUnit (4.5) em um método qualquer chamado em uma aplicação web. Porém a classe JUnit não consta na mesma biblioteca de classes ou fontes desta aplicação web, mas sim em outro diretório. Desta forma, escrevi uma classe com método auxiliar que carregasse uma instância desta classe JUnit externa e me retornasse um Class. De posse do Class deste JUnit, posso executá-lo com JUnitCore.runClasses( Class <?>… classes ). Bom, vamos aos códigos:

Classe e método que me retornam o Class de uma classe JUnit:

[code]
package pucrs.tcc.core;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class TCClassLoader {

private static final TCClassLoader instance = new TCClassLoader();

private TCClassLoader() { }


public Class &lt;?&gt; loadClass( String name, String testClassesPath ) {

	try {

		File file = new File( testClassesPath );

		URL myUrl = file.toURL();
		URL urls [] = new URL[] { myUrl };

		ClassLoader classLoader = new URLClassLoader( urls );
		return classLoader.loadClass( name );

	} catch ( MalformedURLException e ) {
		e.printStackTrace();
	} catch ( ClassNotFoundException e ) {
		e.printStackTrace();
	}

	return null;
}

public static TCClassLoader getInstance() {
	return instance;
}

}[/code]
Método que usa TCClassLoader.loadClass() e executa o JUnit (por ex, um doGet()):

protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {

		Class &lt;?&gt; c = TCClassLoader.getInstance().loadClass( &quot;pucrs.tcc.test.FooTest&quot;, &quot;/Users/matheusdutrademoura/Documents/workspace/Tests/bin&quot; );

		Result result = JUnitCore.runClasses( c );

		List &lt;Failure&gt; failures = result.getFailures();

		for ( Failure f : failures ) {
			System.out.println( f.getTestHeader() );
			System.out.println( f.getMessage() );
			System.out.println( f.getException() );
			System.out.println( f.getTrace() );
		}

	}

A classe JUnit:

[code]package pucrs.tcc.test;

import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class FooTest {

@Test public void fTest() {
	assertTrue( false );
}

@Test public void gTest() {
	assertTrue( true );
}

}[/code]

Agora o problema: Se eu executar este mesmíssimo código, com o mesmíssimo junit-4.5.jar em uma aplicação standalone, funciona perfeitamente. Porém, se eu tento fazê-lo por uma aplicação web, estoura a seguinte exception ao chamar JUnitCore.runClasses():

initializationError(pucrs.tcc.test.FooTest) No runnable methods java.lang.Exception: No runnable methods java.lang.Exception: No runnable methods at org.junit.runners.BlockJUnit4ClassRunner.validateInstanceMethods(BlockJUnit4ClassRunner.java:154) at org.junit.runners.BlockJUnit4ClassRunner.collectInitializationErrors(BlockJUnit4ClassRunner.java:112) at org.junit.runners.ParentRunner.validate(ParentRunner.java:253) at org.junit.runners.ParentRunner.&lt;init&gt;(ParentRunner.java:55) at org.junit.runners.BlockJUnit4ClassRunner.&lt;init&gt;(BlockJUnit4ClassRunner.java:56) at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:13) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:93) at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:84) at org.junit.runners.Suite.&lt;init&gt;(Suite.java:66) at org.junit.runner.Request.classes(Request.java:68) at org.junit.runner.JUnitCore.run(JUnitCore.java:107) at org.junit.runner.JUnitCore.runClasses(JUnitCore.java:66) at com.foo.FooServlet.doGet(FooServlet.java:24) at javax.servlet.http.HttpServlet.service(HttpServlet.java:690) at javax.servlet.http.HttpServlet.service(HttpServlet.java:803) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Thread.java:613)
Na aplicação web tudo funciona, com exceção da chamada ao JUnit em si. Posso acessar qualquer coisa da classe retornada via reflection tranqüilamente. Já pesquisei sobre isso no Google (por 2 dias inteiros!). Se eu postei o problema aqui é pq definitivamente não sei mais o que fazer :sad:

Dificil essa hein…

Já tentou usar o JUnit de forma clássica? Com extends TestCase e os métodos começando com test…

Isso pq o método que dá a exception no junit é bem a que procura por métodos com a annotation @Test

Pode acontecer uma certa confusão entre ClassLoader, especialmente se tu estiver num application server.
Não tenho ideia se é isso, mas achei estranha linha 25 do TCClassLoader.
Pq não tenta algo assim:

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

Eu uso URLClassLoader justamente para buscar uma classe que está em outro diretório. Se eu usar esse que tu comentou, ele vai procurar somente por classes que estão no container em si acredito.

Tem razão.
Já pensou em inserir esse teu diretório no classpath em runtime?
Chamando

URLClassLoader.addURL(....)

Edit:
Só esqueci de comentar que o método é protected, então tem que fazer algo assim:

Class sysclass = URLClassLoader.class;

try {
    Method method = sysclass.getDeclaredMethod("addURL", parameters);
    method.setAccessible(true);
    method.invoke(classLoader, new Object[] { u });
} catch (Throwable t) {
    t.printStackTrace();
    throw new IOException(
            "Error, could not add URL to system classloader");
}

Agora mudei o classloader pra isso:

URLClassLoader loader = new URLClassLoader( new URL[] { new URL( "file://Users/matheusdutrademoura/Documents/workspace/Tests/bin" ) } ); return loader.loadClass( "pucrs.tcc.test.FooTest" );
Mas aí a exceção é java.lang.ClassNotFoundException: pucrs.tcc.test.FooTest. Mas sim, o .class está lá.

EDIT: Bom, usei agora "file:/Users/…" com uma “/” somente e o erro do No runnable methods persiste :frowning:

Analisando a classe retornada pelo classloader por reflection, descobri que quando recebo uma referência dela pela aplicaçãozinha standalone os seus métodos vem com as devidas annotations. Porém, na aplicação web os métodos vem sem as annotations do JUnit! 8O

Estranho, então eu faria um teste sem annotations, estendendo do TestCase e
colocando métodos começando com test, só pra ver se funciona.

A propósito, qual server tu usa?

Eu já havia tentado isso antes e nada. Uso o Tomcat 6.

Indo mais além, usei reflection para abrir a classe toda retornada pelo meu classloader e posso ver que os métodos de teste de fato contém a annotation @Test do JUnit, mas mesmo assim a API do JUnit diz não conter nada para testar… Agora, se eu ponho esta classe de teste nos mesmos pacotes da aplicação web em si e a uso com Class.forName(), funciona normalmente… Ou seja, só pode ser problema de classloading!

Vixi… testei aqui exatamente como tu fez e aconteceu a mesma coisa, mesmo erro.