はじめに
こんにちは、NX開発推進部プロダクト開発第1G の本多啓路です。 先日DroidKaigi2025に参加し、多くのセッションを聴きました。その中で「プロパティベーステストによるUIテスト: LLMによるプロパティ定義生成でエッジケースを捉える」というセッションにおいてプロパティベーステストを使ったUIテストの考え方に凄く納得できました。今回は自分なりにプロパティベーステストを使用してUIテストを実装してみようと思います。
プロパティベーステスト(PBT)
PBTは「どのような入力に対しても、常に満たすべき性質(プロパティ)」が守れているかを検証する手法です。一般的にはアルゴリズムの検証やコンポーネント間のインターフェーステストなどに効果的なテストになりますが、UIコンポーネント自体が持つべき「あるべき姿」をプロパティとして捉えることでUIテストにも応用が可能です。
今回は、同期と一緒にOJTで制作した「何食べネーター」というユーザーの回答をもとに飲食店を提案するアプリのUIテストにPBTを応用してみました。
UIテスト項目の洗い出し
アクションにより受け取る情報でUIコンポーネントの状態や挙動がどうなれば「あるべき姿」なのかを考えながら、画面ごとにUIテストができそうな項目を洗い出してみます。
ホーム

- 聞いてみるボタン押下で質問画面に遷移すること
- 右上のスイッチをクリックしてダークモードに切り替わること
- テーマ設定が保存され再読み込み時に保持されていること
質問画面

- はいボタンをクリックすると回答が保存され別のトピックの質問になること
- いいえボタンをクリックすると別の質問に変更すること
- トピック内の質問がいいえを押し続ける限り循環すること
- 項目のスキップで次の質問または結果画面に遷移すること
結果画面

- 質問を元にした検索結果が正しく表示されること
- 答えた回答の数と内容が正しく表示されること
- 次へボタンが正しく動作すること
- 検索結果が10件以下の場合、次へボタンがタップできない
- 検索結果が10件以上かつ最後のページでない場合、次へボタンがタップできる
- 最後のページで次へボタンがタップできない
- 前へボタンが正しく動作すること
- 検索結果が10件以下の場合、前へボタンがタップできない
- 検索結果が10件以上かつ最初のページでない場合、前へボタンがタップできる
- 最後のページで前へボタンがタップできない
- もう一度と書かれたボタンを押すと最初の画面に戻ること
テストコードの作成
洗い出した項目からテストコードを実際に作成しました。
今回使用したテストの構成は以下の通りです。
- Kotest
- Robolectric
- JUnit
初期設定部分や後処理部分は以下の通りです。
@RunWith(AndroidJUnit4::class)
class ResultScreenPropertyTest {
@get:Rule
val composeTestRule = createComposeRule()
private lateinit var themePreference: ThemePreference //テーマを使用するので設定
@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
themePreference = ThemePreference(context)
// テスト前に設定をクリア
themePreference.setDarkTheme(false)
}
@After
fun tearDown() {
// テスト後に設定をクリア
themePreference.setDarkTheme(false)
}
一例として”ホーム画面の聞いてみるボタン押下で質問画面に遷移する”におけるテストコードを記載しています。
var navController: NavController? = null
val questionViewModel = QuestionViewModel()
composeTestRule.setContent {
WhatEatTheme {
val nav = rememberNavController()
navController = nav
NavHost( // NavHostを設定してNavigation graphを追加
navController = nav,
startDestination = "main"
) {
composable(route = "main") {
HomeScreen(
navController = nav,
paddingValues = PaddingValues(0.dp),
onThemeChanged = {},
isChecked = false
)
}
composable(route = "question") {
QuestionScreen(
navController = nav,
questionViewModel = questionViewModel,
paddingValues = PaddingValues(0.dp)
)
}
}
}
}
// Composeがすべてのアニメーションや変更を完了するまで待機
composeTestRule.waitForIdle()
// ボタンが見つかることを確認(onNodeWithTagが成功すればボタンは存在する)
// performClickが成功すればボタンは存在し、表示されている
composeTestRule.onNodeWithTag("Button_聞いてみる").performClick()
// Composeがすべてのアニメーションや変更を完了するまで待機
composeTestRule.waitForIdle()
// 画面遷移を確認するために少し待機
Thread.sleep(200)
composeTestRule.waitForIdle()
// 質問画面に遷移したことを確認
navController?.let { nav ->
val routeAfterNavigate = nav.currentBackStackEntry?.destination?.route
assertTrue(
"質問画面に遷移していません。実際のルート: $routeAfterNavigate",
routeAfterNavigate == "question"
)
}
洗い出した項目を実際に実行し、テストがパスしていることが確認できました。

終わりに
PBTを応用したUIテストを作成してみました。普段は事例テストやVRTを作成することが多いため、新しい観点でテストコードを作成できるようになったと思います。 今回はプロパティ定義生成については触れませんでしたが、こちらはAIにテストコードを書いてもらう上で重要な観点だと思うので詳しく知りたい方はぜひセッションを聞いてみてください!
本多啓路