Как jUnit проверить результат кода в другом потоке

У меня есть процесс, который выполняется в потоке (используется как процесс анализа сигнала в реальном времени). Я хочу передать этот поток процессу известному входу, а затем проверить - в jUnit - правильность вывода. У меня есть прослушиватель обратного вызова, который может уведомить меня, когда поток завершит обработку данных, и я могу успешно выполнить утверждения по результату, зарегистрировав сам тест как слушатель.
 Когда эти утверждения терпят неудачу, они делают исключение. Но это исключение не регистрируется как сбой jUnit, по-видимому, потому, что они происходят вне метода тестирования.

Как структурировать мой тест jUnit, чтобы тест завершился с ошибкой после возвращения слушателя? Вот упрощенная версия кода.

 public class PitchDetectionTest extends TestCase 
    implements EngineRunCompleteListener() {
  AudioData            fixtureData;
  PitchDetectionEngine pitchEngine;

  public void setUp() {
    fixtureData = <stuff to load audio data>;
  }

  public void testCorrectPitch() {
    pitchEngine = new PitchEngine(fixtureData);
    pitchEngine.setCompletionListener(this);
    pitchEngine.start();   
    // delay termination long enough for the engine to complete
    try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
  }

  // gets called by the PitchEngine when it has finished processing all data in the
  // fixtureData.   This is the only method defined by interface    
  // EngineRunCompleteListener.
  public void notifyEngineRunComplete() {

    // The real code asserts things about the PitchEngine results.   When they fail, 
    // an exception is thrown that I can see in the console, but this unit test still  
    // shows as 'success' in the jUnit interface.   What I want is to see 
    // testCorrectPitch() fail.
    assertTrue(false);  

  }

}

public class PitchEngine () {
  EngineRunCompleteListener completionListener();
  Thread myThread;

  public void start() {
    // other stuff
    myThread = new Thread(this);
    myThread.start();    
  }

  public void run() {
    while (some condition) {
      // do stuff with the data
    }
    if (engineRunCompleteListener != null) {
      engineRunCompleteListener.notifyEngineRunComplete();
    }
  }

}

Ответы

Ответ 1

У вас уже есть два потока. Ваша нить junit и поток процесса (запущен myThread.start().
В верхней части моей головы я могу представить, по крайней мере, два варианта, которые у вас есть, и все они связаны с переносом утверждения от notifyEngineRunComplete. Например:

  • Вы можете использовать join, чтобы дождаться окончания процесса, а затем выполните ваши утверждения (Javadoc здесь).
  • Вы можете поместить поток junit в режим ожидания, ожидая объекта монитора, а затем в вашей функции обратного вызова уведомьте об этом мониторе. Таким образом, вы узнаете, что процесс завершен.
  • Вы можете использовать объект Executor и Future. Я думаю, что это было бы самым крутым решением, если оно будет работать с вашими классами (Javadoc здесь).

Ответ 2

Общий подход к тестированию threaded/async-кода заключается в блокировании основного тестового потока, захвате любых неудачных утверждений из других потоков (таких как поток, вызывающий ваш слушатель), разблокирование основного тестового потока и сброса и неудачного утверждения. ConcurrentUnit делает это довольно просто:

final Waiter waiter = new Waiter();

bus.registerListener(() -> {
  // Perform assertion in some other thread
  waiter.assertTrue(true);
  waiter.resume();
});

// Wait for resume() to be called
waiter.await(5000);

Ответ 3

Я хочу передать этот поток процессу известному входу, а затем проверить - в jUnit - правильность вывода. У меня есть прослушиватель обратного вызова, который может уведомить меня, когда поток завершит обработку данных, и я могу успешно выполнить утверждения по результату, зарегистрировав сам тест в качестве слушателя.

Вместо того, чтобы начинать отдельный поток для PitchEngine внутри вашего unit test, почему бы не извлечь логику //do stuff with the data в PitchEngine в другой общедоступный метод и просто вызвать это в unit test? Я не могу придумать какой-либо причины фактически порождать поток внутри вашего unit test, поскольку он звучит так, как будто все, что вам действительно волнует (в этом unit test), проверяет логику обработки.

Ответ 4

если вы должны запустить код подтверждения в обратном вызове, оберните метод обратного вызова в try catch. поймать любой throwable и иметь способ передать это исключение обратно в поток junit (какое-то состояние общего потока). поток junit затем просто перебрасывает любые возвращаемые значения.

Ответ 5

join() работает, если у вас есть только один рабочий поток, из которого вы хотите выполнить утверждения. Если у вас есть несколько потоков, которые должны сообщать о утверждениях в основной поток, вам нужно будет использовать другой механизм. Для этого ConcurrentUnit.