问题
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)
.
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.
回答1:
You could also implement an ExecutorService yourself that will run the task in the same thread. For example:
public class CurrentThreadExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
And then you could inherit from AbstractExecutorService
and use this implementation.
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.
回答2:
Google Guava provides a great class called MoreExecutors
which helped me out when testing code that runs in parallel threads via Executor
or ExecutorService
in JUnit. It lets you create Executor
instances 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 Executors
from MoreExecutors
make everything much easier to test since it's not actually parallel in another thread.
See the MoreExecutors
documentation 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 Executor
or ExecutorService
. Then pass in the one from MoreExecutors
.
So in the test file you'd create the mock executor using 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
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()
}
}
回答3:
A few options:
Extract the code out of the executor service and test it 'stand alone' i.e in your example test
executeTask1()
andexecuteTask2()
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()
andexecuteTask2()
without running them in a separate thread.
Use a
CountDownLatch
to 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.sleep
calls 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 ...; } }; }
回答4:
You could use the Future instance returned by executorService.submit(R).
From documentation:
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.
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").
回答5:
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)
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());
}
来源:https://stackoverflow.com/questions/46393697/how-to-unit-test-a-code-snippet-running-inside-executor-service-instead-waiting