エグゼキューターサービスで実行されているコードを単体テストする方法私の状況では、
_public void test() {
Runnable R = new Runnable() {
@Override
public void run() {
executeTask1();
executeTask2();
}
};
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(R);
}
_
単体テストをしているときに、メソッドが実行する検証をいくつか行いたいと思います。
これは、ネットワーク操作を行うため、executorサービスで実行しています。
単体テストでは、このメソッドの実行が完了するまで待つ必要がありました。 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);
}
_
注:executorサービスオブジェクトをこのコンストラクタークラスに渡します。睡眠時間を待つ代わりにテストする良い方法があるかどうか知りたいのですが。
同じスレッドでタスクを実行するExecutorServiceを自分で実装することもできます。例えば:
public class CurrentThreadExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
そして、AbstractExecutorService
から継承して、この実装を使用できます。
Guavaを使用している場合、別の簡単な方法は MoreExecutors.newDirectExecutorService() を使用することです。これは、自分で作成しなくても同じことを行うためです。
いくつかのオプション:
Executorサービスからコードを抽出し、「スタンドアロン」でテストします。つまり、サンプルのテストexecuteTask1()
とexecuteTask2()
を単独で、または一緒に実行しますが、別々に実行することではありません。糸。 「executorサービスオブジェクトをこのコンストラクタクラスに渡す」という事実は、
executeTask1()
およびexecuteTask2()
の動作を個別のスレッドで実行せずに検証するテスト。CountDownLatch
を使用して、executor-serviceのコードでテストスレッドが終了したことを示すことができるようにします。例えば:
// 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 ...;
}
};
}
Google GuavaはMoreExecutors
という素晴らしいクラスを提供しており、JUnitでExecutor
またはExecutorService
を介して並列スレッドで実行されるコードをテストするときに役立ちました。 Executor
インスタンスを作成するだけで、基本的には実際のExecutor
のモックとして、同じスレッドですべてを実行できます。問題は、JUnitが認識していない他のスレッドで実行されるときに発生するので、Executors
からのこれらのMoreExecutors
は、実際には別のスレッドで並列です。
MoreExecutors
のドキュメント https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/MoreExecutors.html をご覧ください
クラスコンストラクターを変更するか、テストでのみ使用する新しいコンストラクターを追加して、独自のExecutor
またはExecutorService
を提供できます。次に、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()
}
}
ExecutorService.submit(R)によって返されるFutureインスタンスを使用できます。
ドキュメントから:
Runnableタスクを送信して実行し、そのタスクを表すFutureを返します。 Futureのgetメソッドは、正常に完了するとnullを返します。
例:
_@Test
void test() throws ExecutionException {
Future<Boolean> result = Executors.newSingleThreadExecutor().submit(() -> {
int answer = 43;
assertEquals(42, answer);
return true;
}
assertTrue(result.get());
}
_
内部アサーションは例外をスローします。これにより、result.get()
が独自の例外をスローします。したがって、テストは失敗し、例外の原因によって理由がわかります(「42が必要でしたが、代わりに43でした」)。
私の場合、@ ejfrancisのコメントに同意します。これはローカル変数であるため、メンバー変数に移動し、そこからリフレクションをテストで使用するだけです(おそらくリフレクションは最善のアプローチではありませんが、変更は少なくなります)
class MyClass {
private final ExecutorService executorService =
Executors.newFixedThreadPool(5);;
}
次に、ここから、次のようなクラスの作成後にテストを行います。
@BeforeEach
void init(){
MyClass myClass = new Class();
ReflectionTestUtils.setField(myClass, "executorService", MoreExecutors.newDirectExecutorService());
}