新たなテストルールの作り方を紹介します。
JUnitとAndroidJUnitにはいくつかの有用なテストルールがすでに用意されています。しかし、十分とは言えません。テストルールを自作してテスト環境を機能拡張しましょう!
テストルールの役割
テストルールはテストに必要なボイラープレートコードを削減します。
ボイラープレートとは「省く事ができない定型的な処理」のことです。テストを記述すると、各テストにボイラープレートコードが頻繁に出てきます。
例えばActivityがありA、B、Cの3つの機能を持つとします。この3つの機能をテストする時、各々のテストには準備でActivityを起動し、終わりでActivityを停止する処理が必要になります。Activityの機能をテストするのですから、Activityの起動と停止が必要なのは当たり前ですよね。
テストクラスにActivityの起動と停止を記述すると次のようになります。この記述がボイラープレートコードです。
@RunWith(AndroidJUnit4::class) class SampleATest { lateinit var activity: Activity @Before fun setUp() { val instrumentation = InstrumentationRegistry.getInstrumentation() val context = instrumentation.targetContext val intent = Intent(context, MainActivity::class.java) activity = instrumentation.startActivitySync(intent) instrumentation.waitForIdleSync() } @After fun tearDown() { activity.finish() } @Test fun 機能Aのユースケース() { // 機能Aのユースケースをチェック } }
@RunWith(AndroidJUnit4.class) public class SampleATest { private Activity mActivity; @Before public void setUp() { Instrumentation _Instrumentation = InstrumentationRegistry.getInstrumentation(); Context _Context = _Instrumentation.getTargetContext(); Intent _Intent = new Intent(_Context, MainActivity.class); mActivity = _Instrumentation.startActivitySync(_Intent); _Instrumentation.waitForIdleSync(); } @After public void tearDown() { mActivity.finish(); } @Test public void 機能Aのユースケース() { // 機能Aのユースケースをチェック } }
AndroidJunitにルールActivityTestRuleがあります。これは例に挙げたActivityの起動と停止を行うテストルールです。ルールActivityTestRuleを使うと次のようになります。
@RunWith(AndroidJUnit4::class) class SampleA_Test { @get:Rule val activityTestRule = ActivityTestRule(MainActivity::class.java) @Before fun setUp() { } @After fun tearDown() { } @Test fun 機能Aのユースケース() { // 機能Aのユースケースをチェック } }
@RunWith(AndroidJUnit4.class) public class SampleA_Test { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class); @Before public void setUp() { } @After public void tearDown() { } @Test public void 機能Aのユースケース() { // 機能Aのユースケースをチェック } }
ボイラープレートコードがなくなり、シンプルになりました。
※実際のルールActivityTestRuleは内部でもっと複雑な処理をしています。
テストルールの作成
テストクラスにボイラープレートコードが出てきたら、新たなテストルールを作ってみましょう。テストルールに置き換えると記述もすっきりするし、作ったテストルールは後々のテストで再利用できるし、一石二鳥ですよ。
作成方法は簡単です。TestRuleを継承したクラスを作り、テストの実行(Statement#evaluate)を前処理(before)、後処理(after)で挟むだけです。
class CustomRule : TestRule { override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { try { before() // 前処理 base.evaluate() // テストの実施 } finally { after() // 後処理 } } } } private fun before() { ここで前処理を行う } private fun after() { ここで後処理を行う } }
public class CustomRule implements TestRule { @Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { before(); // 前処理 base.evaluate(); // テストの実施 } finally { after(); // 後処理 } } }; } private void before() { ここで前処理を行う } private void after() { ここで後処理を行う } }
注意点はtry{…}finally{…}を使い、後処理はfinally{…}に記述することです。これはテストがfailになったとき、後処理が必ず実行されることを保証するためです。
テストルールの処理の順番は?
複数のテストルールをテストクラスに記述したとき、どういう順番で実行されるのか、気になったので調べてみました。
@RunWith(AndroidJUnit4::class) class CustomRule_Test { @get:Rule val ruleA = CustomRule_A() // (3) @get:Rule val ruleB = CustomRule_B() // (2) @get:Rule val ruleC = CustomRule_C() // (1) @Before fun setUp() { Log.i("CustomRule_Test", "Execute setUp") } @After fun tearDown() { Log.i("CustomRule_Test", "Execute tearDown") } @Test fun カスタムルールの順番をテスト() { Log.i("CustomRule_Test", "テストの実施") } }
@RunWith(AndroidJUnit4.class) public class CustomRule_Test { @Rule public CustomRule_A mRuleA = new CustomRule_A(); // (3) @Rule public CustomRule_B mRuleB = new CustomRule_B(); // (2) @Rule public CustomRule_C mRuleC = new CustomRule_C(); // (1) @Before public void setUp() { Log.i("CustomRule_Test", "Execute setUp"); } @After public void tearDown() { Log.i("CustomRule_Test", "Execute tearDown"); } @Test public void カスタムルールの順番をテスト() { Log.i("CustomRule_Test", "テストの実施"); } }
I/CustomRule_C: Execute before I/CustomRule_B: Execute before I/CustomRule_A: Execute before I/CustomRule_Test: Execute setUp I/CustomRule_Test: テストの実施 I/CustomRule_Test: Execute tearDown I/CustomRule_A: Execute after I/CustomRule_B: Execute after I/CustomRule_C: Execute after
順番はC⇒B⇒A⇒テスト⇒A⇒B⇒Cとなります。
ただ、実行順に依存関係のあるルールは、原因が解明しづらいfailを生むことになりそうなので避けるべきだと思います。