Как написать тестовый пример JUnit для тестирования потоков и событий

У меня есть код Java, который работает в одном (основном) потоке. Из основного потока я создаю новый поток, в котором я делаю вызов сервера. После завершения вызова сервера я выполняю некоторую работу в новом потоке, после чего код присоединяется к основному потоку.

Я использую eclipse Jobs для выполнения вызова сервера.

Я хочу знать, как мне написать тестовый пример JUnit для этого.

Ответ 1

Вам может потребоваться реструктурировать свой код, чтобы он мог быть легко протестирован.

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

  • Код управления цепочкой: код, запускающий поток (-ы) и, возможно, ожидающий результатов
  • "Рабочий" код запускается в потоке
  • Проблемы concurrency, которые могут возникнуть при активном использовании нескольких потоков.

Структурируйте свою реализацию, чтобы код управления потоком был агностик относительно деталей Рабочего. Затем вы можете использовать Mock Workers для включения тестирования управления потоками - например, Mock Worker, который не работает определенным образом, позволяет вам проверять определенные пути в коде управления.

Внедрить код Worker, чтобы он мог выполняться изолированно. Вы можете использовать unit test самостоятельно, используя mocks для сервера.

Для concurrency тестирования ссылки, предоставленные Abhijeet Kashnia, помогут.

Ответ 2

Это то, что было создано ConcurrentUnit. Общее использование:

  • Создайте несколько потоков
  • Ожидание или спуск основного потока
  • Выполнять утверждения из рабочих потоков (которые через ConcurrentUnit возвращаются в основной поток)
  • Возобновить основной поток из одного из рабочих потоков после завершения всех утверждений

Дополнительную информацию см. на странице ConcurrentUnit.

Ответ 3

Я предлагаю вам использовать насмешливую структуру, чтобы подтвердить, что вызов сервера действительно сделан. Что касается тестирования нитевого блока: Тестирование многопоточных приложений

Ответ 4

Ресурсы, предоставленные Abhijeet Kashnia, могут помочь, но я не уверен, чего вы пытаетесь достичь.

Вы можете выполнить модульное тестирование с помощью mocks, чтобы проверить ваш код, который не будет тестировать concurrency, но обеспечит покрытие. Вы можете написать интеграционный тест, чтобы убедиться, что потоки создаются и объединяются так, как вы ожидаете. Однако это не будет гарантировать проблемы concurrency. Большинство параллельных проблем вызваны ошибками синхронизации, которые не предсказуемы и, следовательно, не могут быть протестированы точно.

Ответ 5

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

Одна из трудностей в тестировании потоков исходит из их самой природы - они параллельны. Это означает, что вы вынуждены записывать тестовый код JUnit, который вынужден ждать, пока ваш поток не завершит свою работу до тестирования результатов вашего кода. Это не очень хороший способ тестирования кода и может быть ненадежным, но обычно это означает, что у вас есть представление о том, работает ли код.

В качестве примера ваш код может выглядеть примерно так:

@Test
public void myIntegrationTest() throws Exception {

   // Setup your test


   // call your threading code
   Results result = myServerClient.doThreadedCode();

   // Wait for your code to complete
   sleep(5);

   // Test the results
   assertEquals("some value",result.getSomeValue());

}


private void sleep(int seconds) {

    try {
        TimeUnit.SECONDS.sleep(seconds);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Мне действительно не нравится это делать и предпочитаю насмешки и соглашаюсь с другими ответами. Но если вам нужно проверить свои потоки, то это один из подходов, который я нахожу.

Ответ 6

Вот мое решение для тестирования метода asynchrone, который использовал thread.start:

public class MyClass {      
   public void doSomthingAsynchrone() {
      new Thread(() -> {
         doSomthing();
      }).start();
   }

   private void doSomthing() {
   }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class MyClassTest {
   ArgumentCaptor<Runnable> runnables = ArgumentCaptor.forClass(Runnable.class);

   @InjectMocks
   private MyClass myClass;

   @Test
   public void shouldDoSomthingAsynchrone() throws Exception {  

      // create a mock for Thread.class
      Thread mock = Mockito.mock(Thread.class);

      // mock the 'new Thread', return the mock and capture the given runnable
      whenNew(Thread.class).withParameterTypes(Runnable.class)
            .withArguments(runnables.capture()).thenReturn(mock);

      myClass.doSomthingAsynchrone();

      runnables.getValue().run();

      /**
       *  instead of 'runnables.getValue().run();' you can use a real thread.start
       *
       *   MockRepository.remove(Thread.class);
       *   Thread thread = new Thread(runnables.getValue());
       *   thread.start();
       *   thread.join();
       **/

      verify(myClass, times(1)).doSomthing();
   }
}

Ответ 7

Когда ваша единственная проблема ждет результата, используйте ExecutorService для порождения ваших потоков. Он может принимать рабочие задания как Runnable и Callable. Когда вы используете последний, вам дается Future объект взамен, который можно использовать для ожидания результата. В любом случае вам следует рассмотреть возможность использования ExecutorService, поскольку, насколько я понимаю, вы создаете много потоков, и это идеальный вариант использования для служб executor.

class AnyClass {
    private ExecutorService threadPool = Executors.newFixedThreadPool(5);

    public List<Future<Integer>> anyMethod() {
        List<Future> futures = new ArrayList<>();

        futures.add(threadPool.submit(() -> {
            // Do your job here
            return anyStatusCode;
        }));

        futures.add(threadPool.submit(() -> {
            // Do your other job here
            return anyStatusCode;
        }));

        return futures;
    }
}

И тестовый класс:

class TestAnyClass {
    @Test
    public void testAnyMethod() {
        AnyClass anyObject = new AnyClass();

        List<Future<Integer>> futures = anyObject.anyMethod();
        CompletableFuture[] completable = futures.toArray(new CompletableFuture[futures.size()]);

        // Wait for all
        CompletableFuture.allOf(completable).join();
    }
}