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

感情迁移 提交于 2020-06-27 07:00:31

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!