java - Prueba concurrente JUnit
multithreading unit-testing (5)
Tengo un gran conjunto de pruebas JUnit, donde me gustaría ejecutar todas las pruebas al mismo tiempo por dos razones:
- Explota múltiples núcleos para ejecutar todo el conjunto de pruebas más rápido
- Esperemos que detecte algunos errores debido a objetos globales no seguros para subprocesos
Reconozco que esto me obligará a refactorizar algún código para que sea seguro para subprocesos, pero considero que es algo bueno :-)
¿Cuál es la mejor manera de hacer que JUnit ejecute todas las pruebas al mismo tiempo?
¡Al parecer, Mathieu Carbou ha hecho una implementación para la concurrencia que podría ayudar!
http://java.dzone.com/articles/concurrent-junit-tests
OneJunit
@RunWith(ConcurrentJunitRunner.class)
@Concurrent(threads = 6)
public final class ATest {
@Test public void test0() throws Throwable { printAndWait(); }
@Test public void test1() throws Throwable { printAndWait(); }
@Test public void test2() throws Throwable { printAndWait(); }
@Test public void test3() throws Throwable { printAndWait(); }
@Test public void test4() throws Throwable { printAndWait(); }
@Test public void test5() throws Throwable { printAndWait(); }
@Test public void test6() throws Throwable { printAndWait(); }
@Test public void test7() throws Throwable { printAndWait(); }
@Test public void test8() throws Throwable { printAndWait(); }
@Test public void test9() throws Throwable { printAndWait(); }
void printAndWait() throws Throwable {
int w = new Random().nextInt(1000);
System.out.println(String.format("[%s] %s %s %s",Thread.currentThread().getName(), getClass().getName(), new Throwable ().getStackTrace()[1].getMethodName(), w));
Thread.sleep(w);
}
}
Múltiples JUnits:
@RunWith(ConcurrentSuite.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
¿Estás arreglado para JUnit? TestNG proporciona una buena prueba de múltiples hilos de fábrica y es compatible con las pruebas JUnit (debe realizar algunos cambios). Por ejemplo, podría ejecutar una prueba como esta:
@Test(threadPoolSize = 3, invocationCount = 9, timeOut = 10000)
public void doSomething() {
...
}
Esto significaría que el método doSomething()
se invocará 9 veces por 3 hilos diferentes.
Recomiendo TestNG .
El siguiente código debe cumplir con sus requisitos, que se tomó del libro alemán JUnit Profiwissen, que contiene algunos consejos para probar cosas en paralelo o sobre cómo reducir el tiempo de ejecución mediante el uso de varios núcleos en lugar de un solo núcleo.
JUnit 4.6 introdujo una clase ParallelComputer que ofrecía la ejecución paralela de pruebas. Sin embargo, esta funcionalidad no era públicamente accesible hasta JUnit 4.7, que brindaba la posibilidad de establecer un planificador personalizado para el corredor primario.
public class ParallelScheduler implements RunnerScheduler {
private ExecutorService threadPool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
@Override
public void schedule(Runnable childStatement) {
threadPool.submit(childStatement);
}
@Override
public void finished() {
try {
threadPool.shutdown();
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Got interrupted", e);
}
}
}
public class ParallelRunner extends BlockJUnit4ClassRunner {
public ParallelRunner(Class<?> klass) throws InitializationError {
super(klass);
setScheduler(new ParallelScheduler());
}
}
Si ahora anota una clase de prueba con @RunWith(ParallelRunner.class)
cada método se ejecutará dentro de su propio hilo. Además, habrá tantos hilos activos (solo) como núcleos de CPU disponibles en la máquina ejecutora.
Si se deben ejecutar múltiples clases en paralelo, puede definir un conjunto personalizado como este:
public class ParallelSuite extends Suite {
public ParallelSuite(Class<?> klass, RunnerBuilder builder)
throws InitializationError {
super(klass, builder);
setScheduler(new ParallelScheduler());
}
}
y luego cambie @RunWith(Suite.class)
con @RunWith(ParallelSuite.class)
Incluso puede aprovechar la funcionalidad de la WildcardPatternSuite
extendiéndose directamente desde esa suite en lugar de Suite
como en el ejemplo anterior. Esto le permite además filtrar pruebas de unidad fe por cualquier @Category
- una TestSuite que solo ejecuta categorías anotadas UnitTest
en paralelo podría verse así:
public interface UnitTest {
}
@RunWith(ParallelSuite.class)
@SuiteClasses("**/*Test.class")
@IncludeCategories(UnitTest.class)
public class UnitTestSuite {
}
Un caso de prueba simple ahora podría verse así:
@Category(UnitTest.class)
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
@Test
public void testSomething() {
...
}
}
UnitTestSuite
ejecutará cada clase que se encuentre en subdirectorios que termine con Test
y tenga una @Category(UnitTest.class)
especificada en paralelo, dependiendo de la cantidad de núcleos de CPU disponibles.
No estoy seguro de si puede ser más simple que eso :)
Estaba buscando una respuesta a esta pregunta, y basándome en las respuestas aquí y en lo que leí en otros lugares, parece que actualmente no existe una manera sencilla de ejecutar pruebas existentes en paralelo usando JUnit. O si hay, no lo encontré. Así que escribí un simple JUnit Runner que logra eso. Por favor sientete libre de usarlo; ver http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ para una explicación completa y el código fuente de la clase MultiThreadedRunner. Con esta clase, puede anotar sus clases de prueba existentes de esta manera:
@RunWith(MultiThreadedRunner.class)
También puedes probar HavaRunner . Es un corredor JUnit que ejecuta pruebas en paralelo de forma predeterminada.
HavaRunner también tiene suites prácticas: puede declarar una prueba para ser miembro de una suite agregando la anotación @PartOf(YourIntegrationTestSuite.class)
a la clase. Este enfoque difiere del de JUnit, donde declaras las membresías de la suite en la clase suite.
Además, las suites HavaRunner pueden inicializar objetos pesados, como un contenedor de aplicaciones web integrado. HavaRunner luego pasa este objeto de gran peso al constructor de cada miembro de la suite. Esto elimina la necesidad de las anotaciones @BeforeClass
y @AfterClass
, que son problemáticas porque promueven el estado mutable global, lo que a su vez dificulta la paralelización.
Por último, HavaRunner tiene escenarios: una forma de ejecutar la misma prueba con diferentes datos. Los escenarios reducen la necesidad de duplicar el código de prueba.
HavaRunner ha sido probado en batalla en dos proyectos medianos de Java.
PD. Soy el autor de HavaRunner, y agradecería sus comentarios al respecto.