如何编写JUnit测试用例以测试线程和事件

19

我有一段Java代码,它在一个主线程中运行。从主线程中,我创建了一个新的线程,在新线程中进行了一次服务器调用。等待服务器响应后,我在新线程中做了一些工作,然后代码再次回到主线程。

我使用Eclipse Jobs来执行服务器调用。

我想知道如何为这个代码编写一个JUnit测试用例。

7个回答

12

你可能需要重构你的代码,使其更易于测试。

我可以看到几个明显的测试领域:

  1. 线程管理代码:启动线程并等待结果的代码
  2. 在线程中运行的“工作者”代码
  3. 多个线程活动时可能导致的并发问题

结构化你的实现,使你的线程管理代码对工作者的细节无所谓。然后,你可以使用模拟工作者来测试线程管理 - 例如,一个在某些方面失败的模拟工作者允许你测试管理代码中的某些路径。

实现工作者代码使其能够独立运行。然后,你可以使用服务器的模拟来单独对其进行单元测试。

对于并发测试,Abhijeet Kashnia提供的链接将有所帮助。


6
这是我创建ConcurrentUnit的原因。通常的使用方法如下:
  1. 启动一些线程
  2. 让主线程等待或休眠
  3. 在工作线程中执行断言(通过ConcurrentUnit返回给主线程)
  4. 所有断言完成后,从工作线程中的一个恢复主线程
请参阅ConcurrentUnit页面获取更多信息。

3
Abhijeet Kashnia 提供的资源可能有所帮助,但我不确定您想要实现什么目标。
您可以使用模拟进行单元测试以验证代码,这不会测试并发性,但会提供覆盖率。 您可以编写集成测试来验证线程是否按照您的期望方式创建和加入。然而,这不能保证避免并发问题。大多数并发问题是由于定时错误引起的,这些错误是不可预测的,因此无法准确地进行测试。

那么你的意思是多线程代码测试不能自动化吗? - displayName
你可以编写测试并使其运行,但是根据我的经验,多线程测试很容易出错并且无法保护你免受时间问题的影响。 - Jean

3

当你唯一的问题是等待结果时,使用 ExecutorService 来生成线程。它可以接受工作任务作为 RunnableCallable。当你使用后者时,你将得到一个 Future 对象作为返回值,用于等待结果。无论如何,您都应该考虑使用 ExecutorService,因为从我的理解来看,您创建了许多线程,而这正是执行器服务的完美用例。

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();
    }
}

2

我猜您可能已经完成了模拟代码,并希望进行简单的集成测试,以确保您的服务器调用起作用。

测试线程存在的一个困难在于它们的本质-它们是并发的。这意味着您被迫编写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();
    }
}

我真的不喜欢这样做,更喜欢模拟,并且同意其他答案。但是,如果您需要测试您的线程,则这是我发现有效的一种方法。


2

1

这是我对使用 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();
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接