Как подождать поток, который порождает его собственный поток?
Я пытаюсь протестировать метод, который работает в отдельном потоке, упростил его следующим образом:
public void methodToTest()
{
Thread thread = new Thread()
{
@Override
public void run() {
Clazz.i = 2;
}
};
thread.start();
}
В моем unit test я хочу проверить, что Clazz.i == 2, но я не могу этого сделать, потому что я думаю, что assert выполняется до того, как поток изменит значение. Я думал использовать другой поток, чтобы проверить его, а затем использовать соединение, чтобы ждать, но он все еще не работает.
SSCCE:
@Test
public void sscce() throws InterruptedException
{
Thread thread = new Thread()
{
@Override
public void run() {
methodToTest()
}
};
thread.start();
thread.join();
AssertEquals(2, Clazz.i);
}
public static class Clazz
{
public static int i = 0;
}
Я думаю, что это связано с тем, что основной код теста создает поток, который ждет (присоединяется) ко второму потоку, но второй поток не выполняет работу, он создает другой поток для выполнения работы и затем заканчивает, что продолжает первый поток, а третий поток выполняет Clazz.i = 2
после утверждения.
Как я могу сделать это так, чтобы первый поток ожидал, что поток запускает , а также любые потоки, которые этот поток запускает?
Ответы
Ответ 1
Без ссылки на поток, созданный в methodToTest
, вы не можете, просто. Java не предоставляет механизма для поиска "потоков, которые были порождены в течение этого определенного периода времени" (и даже если бы это было так, возможно, это был бы уродливый механизм для использования).
Как я вижу, у вас есть два варианта:
- Сделайте
methodToTest
дождитесь появления нити. Конечно, если вы явно хотите, чтобы это было асинхронным действием, вы не можете это сделать.
- Верните созданный поток из
methodToTest
, чтобы все вызывающие абоненты могли подождать, если они этого пожелают.
Можно отметить, что второй выбор можно сформулировать несколькими путями. Например, вы можете вернуть какой-то абстрактный Future
-подобный объект, а не поток, если вы хотите расширить свободу methodToTest
, чтобы использовать различные способы выполнение асинхронной работы. Возможно, вы также можете определить какой-то глобальный пул задач, который обеспечит выполнение всех ваших асинхронных задач внутри, а затем дождитесь завершения всех задач в пуле, прежде чем проверять утверждение. Такой пул задач может принимать форму ExecutorService
или ThreadGroup
, или любой количество других форм. В конце концов все они делают одно и то же, но могут быть более или менее подходящими для вашей среды - главное, что вы должны явно предоставить доступ вызывающего абонента к вновь созданному потоку, каким-то образом.
Ответ 2
Поскольку ваши потоки, похоже, выполняют разные операции, вы можете использовать CountDownLatch, чтобы решить вашу проблему.
Объявите CountDownLatch
в основном потоке и передайте этот объект защелки другим потокам. используйте wait() в основном потоке и уменьшите фиксацию в других потоках.
В главной теме: (первая нить)
CountDownLatch latch = new CountDownLatch(2);
/* Create Second thread and pass the latch. Pass the same latch from second
thread to third thread when you are creating third thread */
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Передайте эту защелку на второй и третий потоки и используйте обратный отсчет в этих потоках
Во втором и третьем потоках
try {
// add your business logic i.e. run() method implementation
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
Посмотрите статью для лучшего понимания.
ExecutorService invokeAll
() API - другое предпочтительное решение.
Ответ 3
Вы не можете выполнять функциональные блокировки, которые устройство не предоставляет.
Вы говорите, что хотите проверить, что methodToTest()
в конечном итоге устанавливает Clazz.i=2
, но что означает "в конце концов"? Ваша функция methodToTest()
не предоставляет вызывающему абоненту любой способ узнать, когда был установлен Clazz.i
. Причина, по которой вам сложно определить, как тестировать эту функцию, заключается в том, что ваш модуль не предоставляет эту функцию.
Это может быть приятное время для ознакомления с Test Driven Development (TDD). Это то, где вы сначала пишете тесты, а затем вы пишете код, который пропускает тесты. Написание тестов сначала поможет вам нарисовать более четкое представление о том, что вы хотите от модуля.
Он также имеет побочное преимущество: если вы практикуете строгую TDD (т.е. если вы никогда не пишете какой-либо код модуля, кроме как сделать тестовый проход), то ваше тестовое покрытие будет на 100%.
И это приводит к другому преимуществу: если у вас есть 100% -ное покрытие для тестирования, то вы можете рефакторировать без страха, потому что, если вы вообще что-то сломаете, ваши тесты на устройства скажут вам об этом.