web-dev-qa-db-ja.com

Thread.sleep(time)を待たずに、executorサービス内で実行されているコードスニペットを単体テストする方法

エグゼキューターサービスで実行されているコードを単体テストする方法私の状況では、

_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サービスオブジェクトをこのコンストラクタークラスに渡します。睡眠時間を待つ代わりにテストする良い方法があるかどうか知りたいのですが。

11
samuel koduri

同じスレッドでタスクを実行するExecutorServiceを自分で実装することもできます。例えば:

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

そして、AbstractExecutorServiceから継承して、この実装を使用できます。

Guavaを使用している場合、別の簡単な方法は MoreExecutors.newDirectExecutorService() を使用することです。これは、自分で作成しなくても同じことを行うためです。

14
claudio

いくつかのオプション:

  • Executorサービスからコードを抽出し、「スタンドアロン」でテストします。つまり、サンプルのテストexecuteTask1()executeTask2()を単独で、または一緒に実行しますが、別々に実行することではありません。糸。 「executorサービスオブジェクトをこのコンストラクタクラスに渡す」という事実は、

    • Executorサービスを模倣し、正しいrunnableを送信することを確認するテスト
    • 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 ...;
            }
        };
    }
    
1
glytching

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

ExecutorService.submit(R)によって返されるFutureインスタンスを使用できます。

ドキュメントから:

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

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でした」)。

1
mrtexaz

私の場合、@ ejfrancisのコメントに同意します。これはローカル変数であるため、メンバー変数に移動し、そこからリフレクションをテストで使用するだけです(おそらくリフレクションは最善のアプローチではありませんが、変更は少なくなります)

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

次に、ここから、次のようなクラスの作成後にテストを行います。

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