等待外部事件,然后再继续进行单元测试

时间:2019-10-18 13:34:03

标签: unit-testing java.util.concurrent grpc-java

上下文: 我正在为gRPC服务编写单元测试。我想验证是否在服务器端调用了模拟方法。我正在使用简单的模拟。为了确保我们能得到gRPC的响应(无论它是什么),我都需要先挂起线程,然后再进行简单的模拟验证调用。

所以我尝试使用LockSupport这样的事情:

  @Test
  public void alphaMethodTest() throws Exception
  {
     Dummy dummy = createNiceMock(Dummy.class);

     dummy.alphaMethod(anyBoolean());
     expectLastCall().once();

     EasyMock.replay(dummy);

     DummyServiceGrpcImpl dummyServiceGrpc = new DummyServiceGrpcImpl();
     bcreuServiceGrpc.setDummy(dummy);


     DummyServiceGrpc.DummyServiceStub stub = setupDummyServiceStub();

     Thread thread = Thread.currentThread();

     stub.alphaMethod(emptyRequest, new StreamObserver<X>(){
         @Override
         public void onNext(X value) {
            LockSupport.unpark(thread);
         }
     }

     Instant expirationTime = Instant.now().plus(pDuration);
     LockSupport.parkUntil(expirationTime.toEpochMilli());

     verify(dummy);
}

但是我有很多这样的测试(大约40个),我怀疑线程问题。我通常会在一到两个未通过验证步骤的情况下,有时全部都通过。我尝试使用带有Condition的ReentrantLock。但是又有一些失败了(signalAll上的IllegalMonitorStateException):

  @Test
  public void alphaMethodTest() throws Exception
  {
     Dummy dummy = createNiceMock(Dummy.class);

     dummy.alphaMethod(anyBoolean());
     expectLastCall().once();

     EasyMock.replay(dummy);

     DummyServiceGrpcImpl dummyServiceGrpc = new DummyServiceGrpcImpl();
     bcreuServiceGrpc.setDummy(dummy);


     DummyServiceGrpc.DummyServiceStub stub = setupDummyServiceStub();


     ReentrantLock lock = new ReentrantLock();

     Condition conditionPromiseTerminated = lock.newCondition();

     stub.alphaMethod(emptyRequest, new StreamObserver<X>(){
         @Override
         public void onNext(X value) {
            conditionPromiseTerminated.signalAll();
         }
     }

     Instant expirationTime = Instant.now().plus(pDuration);
     conditionPromiseTerminated.awaitUntil(new Date(expirationTime.toEpochMilli()));

     verify(dummy);
}

很抱歉,没有为您提供可运行的示例,我当前的代码正在使用私有API:/。

您认为LockSupport可能会因为运行多个测试而引起麻烦?我是否缺少使用锁支持或可重入锁的东西。您是否认为其他任何并发API类都可以更好地满足我的需求?

1 个答案:

答案 0 :(得分:0)

LockSupport 有点危险,您需要仔细阅读文档并了解:

虚假地(即无故)呼叫返回。

因此,当您认为您的代码将进行“等待”时,它可能会立即返回。最简单的原因是this for example,但也可能有其他原因。

使用ReentrantLock时,所有这些都应该以{{1​​}}失败,因为您永远不会通过IllegalMonitorStateException获取锁。并停止使用ReentrantLock::lock,它已被弃用是有原因的。


我认为您过于复杂,可以使用 plain 锁(简化的示例)进行相同的信号发送:

new Date(...)

注意输出:

public static void main(String[] args) {

    Object lock = new Object();

    Thread first = new Thread(() -> {
        synchronized (lock) {
            System.out.println("Locked");
            try {
                System.out.println("Sleeping");
                lock.wait();
                System.out.println("Waked up");
            } catch (InterruptedException e) {
                // these are your tests, no one should interrupt
                // unless it's yourself
                throw new RuntimeException(e);
            }
        }
    });

    first.start();
    sleepOneSecond();

    Thread second = new Thread(() -> {
        synchronized (lock) {
            System.out.println("notifying waiting threads");
            lock.notify();
        }
    });

    second.start();

}

private static void sleepOneSecond() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

很明显,线程之间的“通信”(信号)是如何发生的。