Как подождать поток, который порождает его собственный поток?

Я пытаюсь протестировать метод, который работает в отдельном потоке, упростил его следующим образом:

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% -ное покрытие для тестирования, то вы можете рефакторировать без страха, потому что, если вы вообще что-то сломаете, ваши тесты на устройства скажут вам об этом.