java 如何对在执行程序服务中运行的代码片段进行单元测试,而不是等待 Thread.sleep(time)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46393697/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 09:11:48  来源:igfitidea点击:

How to unit test a code snippet running inside executor service, instead waiting on Thread.sleep(time)

javaunit-testingexecutorservice

提问by samuel koduri

How to unit test a code that is running in executor service? In my situation,

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

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

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

When I am unit testing, I would like to make some validations that method executes.

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

I am executing this in an executor service, as it makes some network operations.

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

In my unit testing, I had to wait until this method finishes execution. Is there a better way I can do this, instead of waiting for Thread.sleep(500).

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

Unit testing code snippet:

单元测试代码片段:

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

Note: I am passing an executor service object into this constructor class. I would like to know if there is a good way of testing instead of waiting for sleep time.

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

回答by claudio

You could also implement an ExecutorService yourself that will run the task in the same thread. For example:

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

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

And then you could inherit from AbstractExecutorServiceand use this implementation.

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

If you're using Guava, another easy one is to use MoreExecutors.newDirectExecutorService()since that does the same thing without you having to create one yourself.

如果您使用 Guava,另一个简单的方法是使用MoreExecutors.newDirectExecutorService()因为它可以做同样的事情,而无需您自己创建一个。

回答by mrtexaz

You could use the Future instance returned by executorService.submit(R).

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

From documentation:

从文档:

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

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

Submits a Runnable task for execution and returns a Future representing that task. The Future's get method will return null upon successful completion.

提交一个 Runnable 任务以供执行,并返回一个代表该任务的 Future。Future 的 get 方法将在成功完成后返回 null。

Example:

例子:

@Test
void test() throws ExecutionException {
    Future<Boolean> result = Executors.newSingleThreadExecutor().submit(() -> {
        int answer = 43;
        assertEquals(42, answer);
        return true;
    }
    assertTrue(result.get());
}

The inner assertion will throw an exception, which causes result.get()to throw its own exception. Thus the test will fail, and the exception's cause will tell you why ("Expected 42, but was 43 instead").

内部断言会抛出一个异常,这会导致result.get()抛出它自己的异常。因此测试将失败,异常的原因将告诉您原因(“预期为 42,但改为 43”)。

回答by glytching

A few options:

几个选项:

  • Extract the code out of the executor service and test it 'stand alone' i.e in your example test executeTask1()and executeTask2()on their own or even together but just not by executing them in a separate thread. The fact that you are "passing an executor service object into this constructor class" helps here since you could have

    • A test which mocks the executor service and verifies that you submit the correct runnable to it
    • Test(s) which verify the behaviour of executeTask1()and executeTask2()without running them in a separate thread.
  • Use a CountDownLatchto allow your code-in-executor-service to indicate to the test thread when it is finished. For example:

    // 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 
        // ...
    }
    
  • Accept that you have to wait for the other thread to complete and hide the ugliness of Thread.sleepcalls in your test cases by using Awaitility. For example:

    @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 ...;
            }
        };
    }
    
  • 从执行程序服务中提取代码并“独立”测试它,即在您的示例测试中单独或什至一起测试executeTask1()executeTask2()但不是通过在单独的线程中执行它们。您“将执行程序服务对象传递到此构造函数类中”这一事实在这里有所帮助,因为您可以

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

    // 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 
        // ...
    }
    
  • 接受您必须等待另一个线程完成并Thread.sleep使用Awaitility隐藏测试用例中调用的丑陋之。例如:

    @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 ...;
            }
        };
    }
    

回答by ejfrancis

Google Guava provides a great class called MoreExecutorswhich helped me out when testing code that runs in parallel threads via Executoror ExecutorServicein JUnit. It lets you create Executorinstances that just run everything in the same thread, essentially as a mock of a real Executor. The issue is when things get run in other threads that JUnit isn't aware of, so these Executorsfrom MoreExecutorsmake everything much easier to test since it's not actuallyparallel in another thread.

Google Guava 提供了一个很棒的类MoreExecutors,它在测试通过JUnitExecutorExecutorService在并行线程中运行的代码时帮助了我。它允许您创建Executor仅在同一线程中运行所有内容的实例,本质上是作为真实Executor. 问题是当事情在 JUnit 不知道的其他线程中运行时,因此这些ExecutorsfromMoreExecutors使一切都更容易测试,因为它实际上不是在另一个线程中并行的。

See the MoreExecutorsdocumentation https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/MoreExecutors.html

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

You can modify your class constructor, or add a new constructor that you only use in tests, which lets you provide your own Executoror ExecutorService. Then pass in the one from MoreExecutors.

你可以修改你的类构造函数,或者添加一个你只在测试中使用的新构造函数,这样你就可以提供自己的ExecutorExecutorService. 然后传入MoreExecutors.

So in the test file you'd create the mock executor using 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);

Then in your class, you only create a real Executor if it wasn't provided in the constructor

然后在你的类中,如果构造函数中没有提供它,你只创建一个真正的 Executor

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

回答by Damonio

I agree with the comment of @ejfrancis, in my case what I did, is that since it was a local variable, I would move it to be a member variable and from there, I would simply use reflection in the test (probably reflection is not the best approach to go, but it will be less changes)

我同意@ejfrancis 的评论,就我而言,我所做的是,因为它是一个局部变量,所以我会将它移动为一个成员变量,然后我将在测试中简单地使用反射(可能反射是不是最好的方法,但变化会更少)

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

Then from here I would just go in my test after the creation of the class like this:

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

@BeforeEach
void init(){
   MyClass myClass = new Class();
   ReflectionTestUtils.setField(myClass, "executorService", MoreExecutors.newDirectExecutorService());
}