0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マルチモジュールな世の中で、安全なInstrumentationTestを書く

Last updated at Posted at 2024-12-18

この記事は RNIアドベントカレンダー2024 2日目の記事です。

皆さんこんにちは、株式会社リサーチ・アンド・イノベーション Androidエンジニアのねこはみんなかわいいです。

ねこはなぜかわいいのか

この記事で言いたいこと

猫は皆可愛い
InstrumentationTestを書くときには、VisibleForTestingを使うと便利だよ、という内容の記事です。

VisibleForTestingとは?

VisibleForTestingは、テスト用のアノテーションを指します。
「通常アクセスできない(private または protected)場所にあるメンバーやメソッドだけど、テストのためだけにアクセス可能にする」 という意図を明確にするためのものです。

例えばこんな経験は無いでしょうか。

テストを書くために、このメソッドのスコープを
無駄にpublicやprotected,package privateにしないといけない...
テストさえ無ければprivateのままで良いのに...。

こんな時、 @VisibleForTesting を付与するのがおすすめです。

ここからは例を使って解説していきましょう。

例 : HogeActivityにあるprivateなメソッドをInstrumentationTestから呼び出したい

HogeActivityには、ある重要な値を返すprivateなメソッド HogeActivity.getImportantValue() があります。
このメソッドをInstrumentationTestのコードから呼び出したいのですが、マルチモジュール環境下ではActivityのテストクラスを :app 直下に置くため、直接呼び出すことができません。

では、どうすれば良いでしょうか?

getImportantValue()をpublicにするのは、他のクラスやモジュールから不必要にアクセスされる可能性があるため、適切ではありません。

まず、以下のようなアクセサーを介して getImportantValue メソッドにアクセス可能にし、テストコードはこのアクセサーを使うことによって、必要な検証を行えるようにします。
このアクセサーに付与するのが @VisibleForTesting アノテーションです。


import androidx.annotation.VisibleForTesting

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
object HogeActivityAccessor {
    @JvmStatic
    fun callGetImportantValue(activity: HogeActivity): String {
        return activity.getImportantValue()
    }
}

VisibleForTestingを使うと、通常のコードの流れでは触れるべきでないメソッドやフィールド(ここでいうgetImportantValue)をテスト用に例外的に公開する意図を示すことができます。
実際のコード内では、private もしくは protected としてアクセスを制限しつつ、テストコードでは呼び出し可能にするためのセーフガードとして機能します。

InstrumentationTestの例


@RunWith(AndroidJUnit4::class)
class HogeActivityInstrumentationTest {

    @get:Rule
    val activityTestRule = ActivityScenarioRule(HogeActivity::class.java)

    @Test
    fun testGetImportantValueUsingAccessor() {
        activityTestRule.scenario.onActivity { activity ->
            // HogeActivityAccessor を利用して getImportantValue を呼び出す
            val result = HogeActivityAccessor.callGetImportantValue(activity)

            // 結果が期待通りかを検証
            assertEquals("expectedValue", result)
        }
    }
}

otherwise の意味と使い所

1. otherwise = VisibleForTesting.PRIVATE

このアクセサーオブジェクトが 通常は private として扱われるべきであることを示しています。
テストコード(src/test もしくは src/androidTest に配置されたコード)では public 扱いになりますが、それ以外ではコンパイルエラーとなります。

2. otherwise = VisibleForTesting.PROTECTED

アクセスレベルが protected として扱われます。
テスト用コード以外でも、特定のサブクラスやパッケージ内から呼び出したい場合に使うそうです。

3. otherwise = VisibleForTesting.NONE

テストコード以外からは一切アクセスできません。
テスト用コードでは引き続き public としてアクセス可能ですが、プロダクションコードでは private と同じ扱いになります。

(補足)VisibleForTesting.PRIVATEとの違い
NONEにすると、プロダクションコードからは一切アクセスできなくなります。
PRIVATEの場合は 同じクラス内であれば、プロダクションコードからのアクセスが可能です。

:point_up:

よって、NONEはテスト目的以外では絶対に呼び出したくないなど、強い制限をかけたい時に使うのが良さそうです。

今回はこのアクセサー自体がテストのためだけの存在で、同一パッケージからのむやみな参照も防ぎたいため、NONEを指定しました。

実際の動作に影響はないの?

Activity側に用意したgetImportantValue は private なまま、内部ロジックで使用されます。
アクセサーやその内部のメソッドは、通常のアプリ動作中に呼ばれることはありません。

まとめ

重要なメソッドやクラスに対して不用意な外部からのアクセスを遮断しつつ、適切なInstrumentationTestを書くためには、テスト用のアノテーション VisibleForTesting が有効であるということが分かりました。

スコープの範囲をどのくらいにすべきかはケースバイケースですが、VisibleForTestingをつけるクラスの用途と設計に応じて決めるのが良いでしょう。

エンジニア募集中です!

現在、RNIではエンジニアを積極採用中です!(業務委託可)
ご応募の他、

  • ちょっと話をしてみたい or 聞いてみたい
  • エンジニアのキャリアについて悩んでいる
  • 猫に気を取られて内容が頭に入ってこなかった

など、少しでも興味がある方はぜひご連絡ください。

◆会社ホームページ
https://r-n-i.jp/

◆ご応募はこちらより
※カジュアル面談も受付中です。
https://r-n-i.jp/recruit/engineer/

◆どんな人たちが働いてるのか気になる?
https://qiita.com/kotlinObasan/items/b8068e7145175572b671

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?