Ответ 1
Разделение Concurrency для вашего класса
Тестирование материала одновременно затруднено (tm)! GOOS среди других людей, рекомендующих отделить часть Concurrency от частей, которые выполняют определенную работу. Например, если у вас есть Scheduler
, который должен планировать какую-либо задачу на одном или нескольких потоках. Вы можете передать часть, которая отвечает за потоки для вашего планировщика, и просто проверить, что планировщик правильно работает с этим объектом. Это больше похоже на классический стиль тестирования единицы.
Пример с "Планировщик" здесь, это использует насмешливую структуру, чтобы помочь. Если вы не знакомы с этими идеями, не волнуйтесь, они, вероятно, не подходят для вашего теста.
Сказав это, вы, возможно, захотите запустить свой класс "в контексте" многопоточным способом. Кажется, это тот тест, который вы пишете выше. Трюк здесь состоит в том, чтобы держать тест детерминированным. Ну, я говорю, что это пара вариантов.
Детерминированный
Если вы можете настроить ваш тест на прогресс детерминированным способом, ожидая в ключевых точках для условий, которые должны быть выполнены перед движением вперед, вы можете попытаться смоделировать определенное условие для проверки. Это означает понимание того, что вы хотите протестировать (например, заставляя код заходить в тупик) и детерминистически пройти (например, используя абстракции, такие как CountdownLatches
и т.д., Чтобы "синхронизировать" движущиеся части).
При попытке сделать несколько многопоточных тестов синхронизируйте свои движущиеся части, вы можете использовать любую абстракцию Concurrency, доступную вам, но это сложно, потому что ее одновременный; все может случиться в неожиданном порядке. Вы пытаетесь сделать это в своем тесте, используя вызовы sleep
. Обычно нам не нравится спать в тесте, потому что это заставит тест работать медленнее, и когда у вас есть тысячи тестов для запуска, каждый мс подсчитывает. Если вы слишком сильно уменьшаете период сна, тест становится недетерминированным, и заказ не гарантируется.
Некоторые примеры включают
- Выключение тупика с помощью
CountdownLatch
- Настройка потока, который нужно перехватить
Вы заметили одну из ошибок, когда основной тестовый поток завершится до того, как завершатся новые порожденные потоки (используя join
). Другой способ - подождать условия, например, используя WaitFor.
Тестирование на выдержку/нагрузку
Другой выбор - настроить тест для настройки, запуска и спама ваших классов в попытке перегрузить их и заставить их предать некоторую тонкую проблему Concurrency. Здесь, как и в другом стиле, вам нужно настроить конкретное утверждение, чтобы вы могли определить, действительно ли и когда классы предали себя.
Итак, для вас тест, я бы предложил придумать утверждение, чтобы вы могли видеть как положительные, так и отрицательные прогоны против вашего класса и заменять вызовы sleep
(и system.out
. ваш тест от чего-то вроде JUnit более похож на идиосинкразированный.
Например, тест basic в стиле, который вы начали, может выглядеть так:
public class TestDriver {
private static final CyclicBarrier barrier = new CyclicBarrier(3);
private static final AtomicInteger counter = new AtomicInteger(0);
static class Runnable1 implements Runnable {
public void run() {
try {
barrier.await();
counter.getAndIncrement();
} catch (Exception ie) {
throw new RuntimeException();
}
}
}
@Test (timeout = 200)
public void shouldContinueAfterBarrier() throws InterruptedException {
Thread t1 = new Thread(new Runnable1());
Thread t2 = new Thread(new Runnable1());
Thread t3 = new Thread(new Runnable1());
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
assertThat(counter.get(), is(3));
}
}
Если возможно, добавление тайм-аута к вашему Барьеру - хорошая практика и поможет написать отрицательный тест, подобный этому
public class TestDriver {
private static final CyclicBarrier barrier = new CyclicBarrier(3);
private static final AtomicInteger counter = new AtomicInteger(0);
static class Runnable1 implements Runnable {
public void run() {
try {
barrier.await(10, MILLISECONDS);
counter.getAndIncrement();
} catch (Exception ie) {
throw new RuntimeException();
}
}
}
@Test (timeout = 200)
public void shouldTimeoutIfLastBarrierNotReached() throws InterruptedException {
Thread t1 = new Thread(new Runnable1());
Thread t2 = new Thread(new Runnable1());
t1.start();
t2.start();
t1.join();
t2.join();
assertThat(counter.get(), is(not((3))));
}
}
Если вы хотите опубликовать свою реализацию, мы можем предложить больше альтернатив. Надеюсь, это даст вам некоторые идеи, хотя...
РЕДАКТИРОВАТЬ: Еще один выбор заключается в том, чтобы добраться до вашего объекта барьера для более мелких зернистых утверждений, например,
@Test (timeout = 200)
public void shouldContinueAfterBarrier() throws InterruptedException, TimeoutException {
Thread t1 = new Thread(new BarrierThread(barrier));
Thread t2 = new Thread(new BarrierThread(barrier));
Thread t3 = new Thread(new BarrierThread(barrier));
assertThat(barrier.getNumberWaiting(), is(0));
t1.start();
t2.start();
waitForBarrier(2);
t3.start();
waitForBarrier(0);
}
private static void waitForBarrier(final int barrierCount) throws InterruptedException, TimeoutException {
waitOrTimeout(new Condition() {
@Override
public boolean isSatisfied() {
return barrier.getNumberWaiting() == barrierCount;
}
}, timeout(millis(500)));
}
EDIT: я написал некоторые из них на http://tempusfugitlibrary.org/recipes/2012/05/20/testing-concurrent-code/