Test Orchestratorというツールを紹介します。
音楽系の単語が使われたネーミングはカッコいいですね。でも、名前から何ができるツールなのか、さっぱり分かりません。
簡単に言えば「テスト毎に独立した実行環境を作る」ツールです。
目次
Test Orchestratorとは
ある時点でパーミッションを許可すると、その後のテストは許可状態でテストが行われてしまいます。不許可の時に表示される警告メッセージをテストしたくても行えません。
このように、前段のテストから影響を受けてしまい、テストの要求が実現しにくくなることがよくあります。
テスト中に加えた変更(上記ではパーミッションの許可)を元に戻せばよいのですが、元に戻せないものもあるし、元に戻すコードを記述する手間が発生します。
さて「どうするか?!」です。この問題をTest Orchestratorが解決してくれます。
Test Orchestratorを使うことで次にあげるメリットが得られます。
- テスト開始時にCPU・メモリは初期状態からスタートする(※1)
- テスト終了後にパッケージに関連したデータをクリアできる(※2)
- クラッシュしたテストのみが停止し、他のテストは継続する(※3)
これらメリットのおかげで、現在のテストは前段から影響を受けることはありません。また、現在のテストは後段へ影響を与えることはありません。
つまり「テスト毎に独立した実行環境」を作ることができます。
Test Orchestratorのさらなる情報はここを参照してください。
環境設定
Test Orchestratorはインストゥルメンテーションで利用できるツールです。
Android Studioにプリインストールされているので有効化を行えば使えます。
有効化するためにbuild.gradleへ次の記述を追加します。
android { ... defaultConfig { ... testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // The following argument makes the Android Test Orchestrator run its // "pm clear" command after each test invocation. This command ensures // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' } ... testOptions { execution 'ANDROIDX_TEST_ORCHESTRATOR' } } dependencies { ... androidTestImplementation 'androidx.test:runner:1.2.0' androidTestUtil 'androidx.test:orchestrator:1.2.0' }
※Android Studio≧Ver 3.0が必要
※Runnner≧Ver 1.0が必要
テスト毎に独立した実行環境
Test Orchestratorを有効にした時、テストが実行される様子を説明します。
実行される様子を考えるとメリットが得られる理由が見えてきます。
テスト毎に新規プロセスで実行
次のようなテストを一括で実行した場合を考えます。
@RunWith(AndroidJUnit4::class) class Class_A { @get:Rule var activityRule = ActivityTestRule(MainActivity::class.java) @Test fun Method_1() { } @Test fun Method_2() { } @Test fun Method_3() { } }
@RunWith(AndroidJUnit4.class) public class Class_A { @Rule public ActivityTestRule mActivityRule = new ActivityTestRule(MainActivity.class); @Test public void Method_1() { } @Test public void Method_2() { } @Test public void Method_3() { } }
Test Orchestratorが有効化されていると、テストは図に示すような環境で実行されます。
※赤枠のプロセスとAndroid Studioの間にOrchestratorというプロセスが入りますが省略されています。
テスト(テストメソッド)毎にプロセスが作成され、その中でRunnerとMainのスレッドが起動されます。テストはそのスレッドで実行されます。
注目すべき点はテスト毎にプロセスが作成されることです。
プロセスが作成されると個別のメモリ(仮想メモリ)が新規に割当て(※1)られます。また、このメモリは保護機能(MMU:Memory Management Unitの機能)が働いて、他のプロセスからアクセスされません。その逆も同じです。
よって、テスト毎のメモリは独立しています。
プロセスが作成されるとOS(Android、ベースはLinux)のスケジューラによってCPU資源の割当てが開始(※1)されます。そのCPU資源によって処理が進みます。CPU資源の割り当てに関してプロセスは受け身の立場であり、他のプロセスのCPU資源に関知しません。その逆も同じです。
よって、テスト毎のCPU資源は独立しています。
アプリ内で致命的な不具合が発生してError(またはException)が投げられると、アプリはその時点でクラッシュします。そして、テスト毎に作成されたプロセスは強制的に停止させられます。
よって、停止するのはクラッシュしたプロセスのみ(※3)です。停止に合わせてメモリとCPU資源も消滅します。
クラッシュしたテストに関係なく、次のテストは新しいプロセスを作成して処理を継続(※3)できます。
パッケージに関連したデータのクリア
パッケージに関連したデータとはアプリ固有のデータです。アプリの初回の起動で格納場所が作成されます。ここに格納されたデータは、アプリがアンインストールされるか、ユーザが意図的に削除するか、どちらかを行うまで保持されます。
次のようなデータが格納されます。
- アプリが作成したファイル
- アプリが作成したデータベース
- アプリが作成したキャッシュ
- パーミッションの許可情報
- etc.
「Settingアプリ⇒App info⇒アプリ名」で表示される項目はこのデータの一部です。
先ほど「ユーザが意図的に削除」と述べたのは、このページで削除の操作を行うことです。
データの具体的な格納場所は次の通りです。
# pwd /data/data/com.example.android.myapp ... /data/data/パッケージ名 # ls -R .: cache databases files ./cache: ... アプリが作成したキャッシュ ./databases: ... アプリが作成したデータベース Sample.db Sample.db-journal ./files: ... アプリが作成したファイル Sample.txt ※'#'はroot権限で実行することを表す
# pwd /data/system/users/0 ... '0'はユーザ番号 # ls -l total 136 -rw------- 1 system system 7581 2020-05-21 00:00 app_idle_stats.xml -rw------- 1 system system 346 2020-05-15 12:36 appwidgets.xml drwx------ 2 system system 4096 2020-04-04 00:00 fpdata -rw-rw---- 1 system system 11937 2020-05-21 00:11 package-restrictions.xml drwxrwx--x 2 system system 4096 2020-04-04 00:00 registered_services -rw------- 1 system system 9461 2020-05-21 00:11 runtime-permissions.xml ... パーミッション情報 -rw------- 1 system system 5665 2020-05-19 23:15 settings_global.xml -rw------- 1 system system 5238 2020-05-07 02:14 settings_secure.xml -rw------- 1 system system 2777 2020-04-04 00:01 settings_system.xml -rw------- 1 system system 176 2020-05-15 12:36 wallpaper_info.xml # cat runtime-permissions.xml ... <pkg name="com.example.android.myapp"> <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" /> <item name="android.permission.CAMERA" granted="true" flags="0" /> <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" /> </pkg> ... ※'#'はroot権限で実行することを表す
「clearPackageData: ‘true’」はテスト終了後に、パッケージに関連したデータのクリアを実行する指定です。「adb shell pm clear」と同等な処理が行われます。※「’false’」で実行しない。
実行すればデータはクリアされて、アプリは初回の起動の直後(データが無い状態、または初期状態)に戻ります(※2)。
Test Orchestratorについて追記
未使用時のテスト実行環境
Test Orchestrator未使用時(有効化しない場合)の実行環境について触れておきます。
有効化した場合との違いを比べれてください。メリットが理解しやすくなると思います。
未使用時のテストは図に示すような環境で実行されます。
テストを実行するためにプロセスが一つ作成されて、その中にRunnerとMainのスレッドが作られます。テストはこの2つのスレッドで順番に実行されます。
注目すべき点はすべてのテストが同じプロセスの同じスレッドで実行されることです。
すべてのテストはメモリとCPU資源を共有しています。何処かのテストで不具合が発生したら、その不具合を持ったメモリとCPUのまま、他のテストが継続していきます。
アプリ内で致命的な不具合が発生してError(またはException)が投げられると、アプリはその時点でクラッシュします。そして、テスト用に作成されたプロセスは強制的に停止させられます。
これはクラッシュした時点で、すべてのテストが停止するということです。残ったテストは実行されません。