解決策:InstrumentationTestCaseにして、setUpメソッドで super.setUp
した後にgetInstrumentation.waitForIdleSync()
を呼ぶべし
期待していた動き
- Appicationクラスが生成される - TestクラスのsetUpメソッドが呼ばれる - Testクラスのtestメソッドが呼ばれる - TestクラスのtearDownメソッドが呼ばれる - TestクラスのsetUpメソッドが呼ばれる - Testクラスのtestメソッドが呼ばれる - TestクラスのtearDownメソッドが呼ばれる
実際の動き
- TestクラスのsetUpメソッドが呼ばれる - Appicationクラスが生成される <------ - Testクラスのtestメソッドが呼ばれる - TestクラスのtearDownメソッドが呼ばれる - TestクラスのsetUpメソッドが呼ばれる - Testクラスのtestメソッドが呼ばれる - TestクラスのtearDownメソッドが呼ばれる
何が困ったかと言うと、Singletonなクラスの初期化処理をsetUpメソッド内で行っているわけだけれども Applicationクラスの中でも初期化処理をしている。その為テスト用の初期化処理が、通常のアプリケーション で使う初期化処理に上書きされてしまい、テストがコケた。
Thread.sleep
で対処したという話もあったけど、setUpの度にスリープされるのは嫌だし、sleepしたところで
環境に依ってはsleepが足りなくて結局コケたりする(実際ローカルではうまく動いたけど、Travis CI上ではコケた)
どうやら InstrumentationTestCase
で取得できる Instrumentation
には waitForIdleSync
なるメソッドがあるらしい。
Instrumentation | Android Developers
Synchronously wait for the application to be idle. Can not be called from the main application thread -- use start() to execute instrumentation in its own thread.
なるほど、わからん。
英語読めないなりに呼んでみると、「アプリケーションと同期するために待つよ。だからメインスレッドじゃ呼べないよ(メインスレッドを待ってるわけだから)。startメソッドを実行したらInstrumentationが実行されるよ」
ということだろうか。わからん。特に後半わからん。
本来の使い方はtestメソッド内で、runOnUiThread
の実行が完了するのを待つために使っているので、多分looperが空になるのを待つっていう認識で良い・・・ハズ。多分。
実際に試してみたところ、setUpメソッド内で superを呼んだ直後にwaitForIdleSyncを実行するように書いたところapplicationクラスが生成されて一通りの処理が終わってから処理が再開された。 ローカルでテストして見たところ、期待通りの動作をしてくれたので、TraviceCI上でも試してみたところうまくどうさしているっぽい。
Singletonなんかにしているからテストで苦労するんだ、なんて言われそうな気もするけどSingletonはSingletonで便利な場面もあるので実に悩ましい話でした まる