LoginSignup
0
0
Android強化月間 - Androidアプリ開発の知見を共有しよう -

android.util.PairはUnitテストではfirst==null & second==null になる

Posted at

環境

  • androidx.core.core : v1.9.0
  • kotlin : v1.8.10
  • compileSDK : 33
  • org.robolectric:robolectric : v4.10.3

起きたこと

class ExampleUnitTest {
    @Test
    fun testA() {
        val pair = Pair(1, 2)

        assert(pair.first == 1)
        assert(pair.second == 2)

    }
}

こんなテストを、実行するとなぜか失敗。

原因

→原因は、ここで呼んでいたPairandroid.util.Pairだったから。

import android.util.Pair

class ExampleUnitTest {
    @Test
    fun testA() {
        val pair = Pair(1, 2)

        assert(pair.first == 1)
        assert(pair.second == 2)

    }
}

Pairについて

Androidアプリを作成する際に使えるPairにはandroid.util.Pairandroidx.core.util.Pairkotlin.Pairがあります。

全て(Kotlinから使う場合)コンストラクタPair<F,S>(F first, S second)を使用してインスタンスを作れますが、android.util.Pairだけは、そのコンストラクタを使用してインスタンスを作ってもUnitテストではfirstsecondは引数に渡したものにならずnullになるみたいです。 注意。

成功するテスト(各Pairfirst secondの値)は以下。

class ExampleUnitTest {
    @Test
    fun android_util_pair() {
        val pair = android.util.Pair(1, 2)

        assert(pair.first == null)
        assert(pair.second == null)

    }

    @Test
    fun androidx_core_util_pair() {
        val pair = androidx.core.util.Pair(1, 2)

        assert(pair.first == 1)
        assert(pair.second == 2)

    }

    @Test
    fun kotlin_pair() {
        val pair = kotlin.Pair(1, 2)

        assert(pair.first == 1)
        assert(pair.second == 2)

    }
}

なぜ?

「Unitテストでは」と強調しているところからも察することができるかもしれないですが、android.util.Pair<F,S>(F first, S second)を使ってインスタンスを作成してもfirstsecondともにnullになるのはUnitテストのみです。
実機で動かした場合は、ちゃんとfirstsecondも渡した値になります。

@Composable
fun Test(name: String, modifier: Modifier = Modifier) {
    val pair = android.util.Pair(1, 2)
    Log.d("test", "${pair.first} ${pair.second}")
}
コンソール
1 2

というのもandroid.util.Pairandroid.util.Logなどと同様に、実際の実装はAndroid端末の方にあるメソッドだからです。

ちなみに①

@RunWith(RobolectricTestRunner::class)をつければandroid.util.PairのUnitテストでもOKです。(Contextなどと同じくです。)

@RunWith(RobolectricTestRunner::class)
class ExampleUnitTest{

    @Mock
    val pair = android.util.Pair.create(1, 2)

    @Test
    fun android_util_pair() {
        assert(pair.first == 1)
        assert(pair.second == 2)
    }
}

ちなみに②

android.util.Pairの公式ドキュメント を見るとandroid.util.Pair<F, S> #create (F f,S s)というstaticメソッドもPairインスタンスを作るメソッドとしてあります。

こちらのメソッドは、@RunWith(RobolectricTestRunner::class)なしUnitテストだと**RuntimeExceptionが発生** / @RunWith(RobolectricTestRunner::class)ありUnitテストだとちゃんと動きます。

@RunWith(RobolectricTestRunner::class)なし
class ExampleUnitTest{

    val pair = android.util.Pair.create(1, 2)

    @Test
    fun android_util_pair() {
        assert(pair.first == 1)
        assert(pair.second == 2)
    }
}
@RunWith(RobolectricTestRunner::class)なし結果
Method create in android.util.Pair not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details.
java.lang.RuntimeException: Method create in android.util.Pair not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details.
	at android.util.Pair.create(Pair.java)
	at com.example.myapplication.ExampleUnitTest.<init>(ExampleUnitTest.kt:18)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)

(略)
@RunWith(RobolectricTestRunner::class)あり
@RunWith(RobolectricTestRunner::class)
class ExampleUnitTest{

    @Mock
    val pair = android.util.Pair.create(1, 2)

    @Test
    fun android_util_pair() {
        assert(pair.first == 1)
        assert(pair.second == 2)
    }
}

@RunWith(RobolectricTestRunner::class)あり結果 -> テストpass

まとめ

プロダクトコードでは3つのPair全てで同じ動きになるものの、Unitテストを書くことを考えるとandroid.util.Pairは使わないようにした方がよさそうです。

  • mockのためのコードを書かなくていい・ビルド時間も削減
  • そもそもmockに置き換えるということは正確性に若干欠ける
  • テスト時にandroid.util.Pair<F, S> #create (F f,S s)の方は例外が発生するのでまだわかりやすいが、android.util.Pair<F,S>(F first, S second)コンストラクタの方は「値が想定と異なっていた」という意味での失敗しか起きないのでテストに失敗したときに原因が瞬時に察せないかもしれない。
    • (まさに私はこれで原因の切り分けにとても時間を使ってしまいました。。。nullではなく、例外発生にしてほしかったなぁというのが正直な気持ち。)
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