如何对执行器服务中运行的代码片段进行单元测试,而不是等待Thread.sleep(时间)

时间:2017-09-24 18:54:56

标签: java unit-testing executorservice

如何对在执行程序服务中运行的代码进行单元测试? 在我的情况下,

public void test() {
    Runnable R = new Runnable() {
        @Override
        public void run() {
            executeTask1();
            executeTask2();
        }
    };

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(R);
}

当我进行单元测试时,我想做一些方法执行的验证。

我在执行程序服务中执行此操作,因为它进行了一些网络操作。

在我的单元测试中,我不得不等到这个方法完成执行。有没有更好的方法可以做到这一点,而不是等待Thread.sleep(500)

单元测试代码段:

@Test
public void testingTask() {
    mTestObject.test();
    final long threadSleepTime = 10000L;
    Thread.sleep(threadSleepTime);
    verify(abc, times(2))
            .acquireClient(a, b, c);
    verify(abd, times(1)).addCallback(callback);
}

注意:我将一个执行程序服务对象传递给此构造函数类。 我想知道是否有一种好的测试方法,而不是等待睡眠时间。

5 个答案:

答案 0 :(得分:9)

您也可以自己实现一个ExecutorService,它将在同一个线程中运行该任务。例如:

public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

然后你可以继承AbstractExecutorService并使用这个实现。

如果您正在使用番石榴,另一个简单的方法是使用MoreExecutors.newDirectExecutorService(),因为这样做可以做同样的事情,而不必自己创建。

答案 1 :(得分:1)

一些选择:

  • 从执行程序服务中提取代码并将其“独立”测试,即在您的示例测试中executeTask1()executeTask2()自己或甚至在一起,但不是通过执行它们一个单独的线程。您正在“将执行程序服务对象传递到此构造函数类”这一事实可以帮助您,因为您可以

    • 模拟执行程序服务并验证您是否向其提交正确的runnable的测试
    • 验证executeTask1()executeTask2()的行为而不在单独的线程中运行它们的测试。
  • 使用CountDownLatch允许您的代码执行程序服务在完成时向测试线程指示。例如:

    // this will be initialised and passed into the task which is run by the ExecutorService 
    // and will be decremented by that task on completion
    private CountDownLatch countdownLatch; 
    
    @Test(timeout = 1000) // just in case the latch is never decremented
    public void aTest() {
        // run your test
    
        // wait for completion
        countdownLatch.await();
    
        // assert 
        // ...
    }
    
  • 接受您必须等待其他线程完成,并使用Awaitility隐藏您的测试用例中Thread.sleep次调用的丑陋。例如:

    @Test
    public void aTest() {
        // run your test
    
        // wait for completion
        await().atMost(1, SECONDS).until(taskHasCompleted());
    
        // assert 
        // ...
    }
    
    private Callable<Boolean> taskHasCompleted() {
        return new Callable<Boolean>() {
            public Boolean call() throws Exception {
                // return true if your condition has been met
                return ...;
            }
        };
    }
    

答案 2 :(得分:0)

您可以使用executorService.submit(R)返回的Future实例。

来自文档:

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#submit(java.lang.Runnable)

提交Runnable任务以执行并返回表示该任务的Future。 未来的get方法将在成功完成后返回null。

答案 3 :(得分:0)

Google Guava提供了一个名为MoreExecutors的很棒的类,它在测试通过JUnit中的ExecutorExecutorService在并行线程中运行的代码时对我有帮助。它使您可以创建Executor实例,它们实际上在同一线程中运行所有实例,本质上是模拟真实的Executor。问题是,当事情在JUnit不知道的其他线程中运行时,Executors中的MoreExecutors使一切变得更容易测试,因为它们实际上不是 并行的另一个线程。

请参见MoreExecutors文档https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/MoreExecutors.html

您可以修改您的类构造函数,或添加仅在测试中使用的新构造函数,从而可以提供自己的ExecutorExecutorService。然后从MoreExecutors中传入一个。

因此,在测试文件中,您将使用MoreExecutors

创建模拟执行器
ExecutorService mockExecutor = MoreExecutors.newDirectExecutorService();

// or if you're using Executor instead of ExecutorService you can do MoreExecutors.newDirectExecutor()

MyService myService = new MyService(mockExecutor);

然后在您的课程中,只有在构造函数中未提供真正的执行器时,您才创建该执行器

public MyService() {}
    ExecutorService threadPool;

    public MyService(ExecutorService threadPool) {
        this.threadPool = threadPool;
    }

    public void someMethodToTest() {
        if (this.threadPool == null) {
            // if you didn't provide the executor via constructor in the unit test, 
            // it will create a real one
            threadPool = Executors.newFixedThreadPool(3);
        }
        threadPool.execute(...etc etc)
        threadPool.shutdown()
    }
}

答案 4 :(得分:0)

我同意@ejfrancis的评论,就我而言,我的看法是,由于它是局部变量,因此我将其移动为成员变量,然后从那里开始,我将在测试中使用反射(反思可能不是最好的方法,但是变化会更少)

SELECT emplid, item_type, acad_year, COUNT(DISTINCT REF1_DESCR)
FROM ps_item_sf
GROUP BY emplid, item_type, acad_year
HAVING MIN(REF1_DESCR) <> MAX(REF1_DESCR);

然后从这里开始,我将在创建这样的类之后进行测试:

class MyClass {
            private final ExecutorService executorService = 
            Executors.newFixedThreadPool(5);;
}