Параллельное тестирование JUnit - PullRequest
65 голосов
/ 11 февраля 2011

У меня есть большой набор тестов JUnit, в котором я бы хотел запустить все тесты одновременно по двум причинам:

  • Использование нескольких ядер для ускорения всего набора тестов
  • Надеюсь обнаружить некоторые ошибки из-за не поточнобезопасных глобальных объектов

Я понимаю, что это заставит меня реорганизовать некоторый код, чтобы сделать его поточно-ориентированным, но я считаю, что это хорошовещь: -)

Какой лучший способ заставить JUnit запускать все тесты одновременно?

Ответы [ 5 ]

27 голосов
/ 24 апреля 2012

Вы привязаны к JUnit? TestNG обеспечивает хорошее многопоточное тестирование "из коробки" и совместимо с тестами JUnit (вам нужно внести несколько изменений).Например, вы можете запустить такой тест:

@Test(threadPoolSize = 3, invocationCount = 9,  timeOut = 10000)
public void doSomething() {
...
}

Это будет означать, что метод doSomething() будет вызываться 9 раз 3 различными потоками.

Я настоятельно рекомендую TestNG .

20 голосов
/ 07 октября 2014

Следующий код должен соответствовать вашим требованиям, который был взят из немецкой книги JUnit Profiwissen , в которой содержатся некоторые советы либо для параллельного тестирования, либо для сокращения времени выполнения за счет использования нескольких ядер вместо толькоодноядерный.

JUnit 4.6 представил класс ParallelComputer, который предлагает параллельное выполнение тестов.Однако эта функциональность не была общедоступной до JUnit 4.7, который предоставлял возможность установить собственный планировщик для родительского бегуна.

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());
    }
}

Если вы теперь аннотируете тестовый класс с помощью @RunWith(ParallelRunner.class), каждый метод будет запускатьсяв своей собственной теме.Кроме того, на исполняющем компьютере будет доступно столько активных потоков (только), сколько ядер ЦП.

Если несколько классов должны выполняться параллельно, вы можете определить специальный набор, например:

public class ParallelSuite extends Suite {

    public ParallelSuite(Class<?> klass, RunnerBuilder builder) 
      throws InitializationError {
        super(klass, builder);
        setScheduler(new ParallelScheduler());
    }
}

, а затем измените @RunWith(Suite.class) на @RunWith(ParallelSuite.class)

Вы даже можете использовать функциональность, например, WildcardPatternSuite, расширив его непосредственно из этого набора вместо Suite, как в предыдущем примере.Это позволяет вам, кроме того, фильтровать юнит-тесты, например, по любому @Category - TestSuite, который параллельно выполняет только UnitTest аннотированные категории, может выглядеть так:

public interface UnitTest {

}

@RunWith(ParallelSuite.class)
@SuiteClasses("**/*Test.class")
@IncludeCategories(UnitTest.class)
public class UnitTestSuite {

}

Теперь простой тестовый пример может выглядеть следующим образомthis:

@Category(UnitTest.class)
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {

    @Test
    public void testSomething() {
        ...
    }
}

UnitTestSuite выполнит каждый класс, найденный в подкаталогах, который заканчивается на Test и имеет @Category(UnitTest.class), указанный параллельно - в зависимости от количества доступных ядер ЦП.

Я не уверен, может ли это быть проще:)

20 голосов
/ 01 января 2013

Я искал ответ именно на этот вопрос, и, основываясь на ответах здесь и на том, что я читал в другом месте, кажется, что в настоящее время нет простого готового способа запуска существующих тестовпараллельно используя JUnit.Или, если есть, я не нашел его.Поэтому я написал простой JUnit Runner, который выполняет это.Пожалуйста, не стесняйтесь использовать его;см. http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ для полного объяснения и исходного кода класса MultiThreadedRunner.С помощью этого класса вы можете просто аннотировать существующие тестовые классы следующим образом:

@RunWith(MultiThreadedRunner.class)
5 голосов
/ 22 июля 2014

Очевидно, Матье Карбу выполнил реализацию для согласования, которое может помочь!

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);
    }
}

Несколько JUnits:

@RunWith(ConcurrentSuite.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
1 голос
/ 08 декабря 2013

Вы также можете попробовать HavaRunner .Это бегунок JUnit, который запускает тесты параллельно по умолчанию.

HavaRunner также имеет удобные наборы: вы можете объявить тест как член набора, добавив аннотацию @PartOf(YourIntegrationTestSuite.class) к классу.Этот подход отличается от JUnit, где вы объявляете членство в наборе в наборе.

Кроме того, пакеты HavaRunner могут инициализировать тяжелые объекты, такие как встроенный контейнер веб-приложения.Затем HavaRunner передает этот тяжелый объект конструктору каждого члена пакета.Это устраняет необходимость в аннотациях @BeforeClass и @AfterClass, которые являются проблематичными, поскольку они поддерживают глобальное изменяемое состояние, что, в свою очередь, затрудняет распараллеливание.

Наконец, HavaRunner имеет сценарии - способ запускаодин и тот же тест с разными данными.Сценарии уменьшают необходимость дублирования тестового кода.

HavaRunner был испытан в бою в двух Java-проектах среднего размера.

Ps.Я являюсь автором HavaRunner, и я буду признателен за ваши отзывы об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...