Подтвердить что ты не робот

Параллельное тестирование JUnit

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

@RunWith(MultiThreadedRunner.class)

Ответ 3

Следующий код должен выполнить ваши требования, которые были взяты из немецкой книги 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), каждый метод будет запускаться в пределах своего потока. Кроме того, будет столько активных потоков (только), поскольку ядра CPU доступны на исполняющей машине.

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

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)

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

public interface UnitTest {

}

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

}

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

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

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

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

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

Ответ 4

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

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 {
}

Ответ 5

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

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

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

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

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

Ps. Я автор HavaRunner, и я буду благодарен вам за отзывы.