Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KotlinとAndroidではじめるテスト駆動開発

 KotlinとAndroidではじめるテスト駆動開発

Androidアプリのテスト駆動開発入門
https://speakerdeck.com/tonionagauzzi/androidapurinotesutoqu-dong-kai-fa-ru-men
の第2版。差分は以下。

1. Jetpack Composeを用いたTDDの実例を追加
2. p23の文字化けなど細かい不具合を修正

tonionagauzzi

July 08, 2024
Tweet

More Decks by tonionagauzzi

Other Decks in Programming

Transcript

  1.  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ☑೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ☑ӳޠ೔ຊαʔόʔͷ૊߹ͤ ☑؆ମࣈ೔ຊαʔόʔͷ૊߹ͤ ☑ൟମࣈ೔ຊαʔόʔͷ૊߹ͤ ☑ͦͷଞ೔ຊαʔόʔͷ૊߹ͤ ☑೔ຊޠถࠃαʔόʔͷ૊߹ͤ ☑ӳޠถࠃαʔόʔͷ૊߹ͤ ☑؆ମࣈถࠃαʔόʔͷ૊߹ͤ

    ☑ൟମࣈถࠃαʔόʔͷ૊߹ͤ ☑ͦͷଞถࠃαʔόʔͷ૊߹ͤ ☑઀ଓઌΛ൑ఆͰ͖ͳ͔ͬͨ৔߹ɺ೔ຊޠ೔ຊαʔόʔͷ૊߹ͤͱͳΔ ☑  ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜ੒ͨ͠63-Λ $ISPNFʹ౉͠ɺϔϧϓϖʔδΛ։͘ CHECK LIST 
  2. // テスト @Test fun `日本語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { //

    Given(前提条件) val language = "ja" val country = "JP" // When(操作) val url = get(language = language, country = country) // Then(結果) Truth.assertThat(url).isEqualTo("https://localhost/help/ja/JP/") } // 実装 fun get(language: String, country: String): String { return "" }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ❌೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ RED 
  3. // テスト @Test fun `日本語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { //

    Given(前提条件) val language = "ja" val country = "JP" // When(操作) val url = get(language = language, country = country) // Then(結果) Truth.assertThat(url).isEqualTo("https://localhost/help/ja/JP/") } // 実装 fun get(language: String, country: String): String { return "https://localhost/help/ja/JP/" }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ GREEN 
  4. // テスト @Test fun `日本語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { //

    Given(前提条件) val language = "ja" val country = "JP" // When(操作) val url = get(language = language, country = country) // Then(結果) Truth.assertThat(url).isEqualTo("https://localhost/help/ja/JP/") } @Test fun `英語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { // Given val language = "en" val country = "JP" // When val url = get(language = language, country = country) // Then Truth.assertThat(url).isEqualTo("https://localhost/help/en/JP/") } // 実装 fun get(language: String, country: String): String { return "https://localhost/help/ja/JP/" }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ❌ ӳޠ೔ຊαʔόʔͷ૊߹ͤ RED 
  5. // テスト @Test fun `日本語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { //

    Given(前提条件) val language = "ja" val country = "JP" // When(操作) val url = get(language = language, country = country) // Then(結果) Truth.assertThat(url).isEqualTo("https://localhost/help/ja/JP/") } @Test fun `英語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { // Given val language = "en" val country = "JP" // When val url = get(language = language, country = country) // Then Truth.assertThat(url).isEqualTo("https://localhost/help/en/JP/") } // 実装 fun get(language: String, country: String): String { if (language == "en") { return "https://localhost/help/en/JP/" } else { return "https://localhost/help/ja/JP/" } }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ✅ ӳޠ೔ຊαʔόʔͷ૊߹ͤ GREEN 
  6. // テスト @Test fun `日本語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { //

    Given(前提条件) val language = "ja" val country = "JP" // When(操作) val url = generateHelpLink(language = language, country = country) // Then(結果) Truth.assertThat(url).isEqualTo("https://localhost/help/ja/JP/") } @Test fun `英語 / 日本サーバー の組合せで、ヘルプページのURLを生成する` { // Given val language = "en" val country = "JP" // When val url = generateHelpLink(language = language, country = country) // Then Truth.assertThat(url).isEqualTo("https://localhost/help/en/JP/") } // 実装 fun generateHelpLink(language: String, country: String): String { val helpPath = when (language) { "en" -> "en/JP" else -> "ja/JP" } return "https://localhost/help/$helpPath/" }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ✅ ӳޠ೔ຊαʔόʔͷ૊߹ͤ REFACTOR 
  7. private val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().start().resume().get() // テスト @Test fun `ログイン画面でヘルプリンクが押されると、生成したURLをChromeに渡し、ヘルプページを開く`()

    { // Given (前提条件: ログイン画面を開いている) val fragment = Fragment() activity.add(fragment) val helpLinkUrl = generateHelpLink(language = "ja", country = "JP") // When (操作:ヘルプリンクを押す) fragment.openBrowser(url = helpLinkUrl) // Then (結果:ChromeがURLを開く) val openedActivity = Shadows.shadowOf(activity).nextStartedActivity val openedPackage = openedActivity.getPackage() Truth.assertThat(openedPackage).isEqualTo("com.android.chrome") val openedUrlString = openedActivity.data.toString() Truth.assertThat(openedUrlString).isEqualTo(helpLinkUrl) } // 実装(ヘルプリンクが押されたときの処理) fun Fragment.openBrowser(url: String) { }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ✅ӳޠ೔ຊαʔόʔͷ૊߹ͤ ✅؆ମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ൟମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ͦͷଞ೔ຊαʔόʔͷ૊߹ͤ ✅೔ຊޠถࠃαʔόʔͷ૊߹ͤ ✅ӳޠถࠃαʔόʔͷ૊߹ͤ ✅؆ମࣈถࠃαʔόʔͷ૊߹ͤ ✅ൟମࣈถࠃαʔόʔͷ૊߹ͤ ✅ͦͷଞถࠃαʔόʔͷ૊߹ͤ ❌  ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜ੒ͨ͠63-Λ $ISPNFʹ౉͠ɺϔϧϓϖʔδΛ։͘  RED
  8. private val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().start().resume().get() // テスト @Test fun `ログイン画面でヘルプリンクが押されると、生成したURLをChromeに渡し、ヘルプページを開く`()

    { // Given (前提条件: ログイン画面を開いている) val fragment = Fragment() activity.add(fragment) val helpLinkUrl = generateHelpLink(language = "ja", country = "JP") // When (操作:ヘルプリンクを押す) fragment.openBrowser(url = helpLinkUrl, errorCallback = { e -> fail(e) }) // Then (結果:ChromeがURLを開く) val openedActivity = Shadows.shadowOf(activity).nextStartedActivity val openedPackage = openedActivity.getPackage() Truth.assertThat(openedPackage).isEqualTo("com.android.chrome") val openedUrlString = openedActivity.data.toString() Truth.assertThat(openedUrlString).isEqualTo(helpLinkUrl) } // 実装(ヘルプリンクが押されたときの処理) fun Fragment.openBrowser( url: String, errorCallback: (e: Exception) -> Unit = { e -> // エラー処理 }, ) { try { val chromeIntent = Intent(Intent.ACTION_VIEW, url.toUri()) chromeIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK chromeIntent.setPackage("com.android.chrome") startActivity(chromeIntent) } catch (e: Exception) { errorCallback(e) } }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ✅ӳޠ೔ຊαʔόʔͷ૊߹ͤ ✅؆ମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ൟମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ͦͷଞ೔ຊαʔόʔͷ૊߹ͤ ✅೔ຊޠถࠃαʔόʔͷ૊߹ͤ ✅ӳޠถࠃαʔόʔͷ૊߹ͤ ✅؆ମࣈถࠃαʔόʔͷ૊߹ͤ ✅ൟମࣈถࠃαʔόʔͷ૊߹ͤ ✅ͦͷଞถࠃαʔόʔͷ૊߹ͤ ✅  ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜ੒ͨ͠63-Λ $ISPNFʹ౉͠ɺϔϧϓϖʔδΛ։͘  GREEN
  9. private val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().start().resume().get() // テスト @Test fun `ログイン画面でヘルプリンクが押されると、生成したURLをChromeに渡し、ヘルプページを開く`()

    { // Given (前提条件: ログイン画面を開いている) composeRule.setContent { HelpLink() } // When (操作:ヘルプリンクを押す) composeRule.onNodeWithTag(testTag = "help_link") .assertIsDisplayed() .performClick() // Then (結果:ChromeがURLを開く) val openedActivity = Shadows.shadowOf(activity).nextStartedActivity val openedPackage = openedActivity.getPackage() Truth.assertThat(openedPackage).isEqualTo("com.android.chrome") val openedUrlString = openedActivity.data.toString() Truth.assertThat(openedUrlString).isEqualTo(helpLinkUrl) } // 実装(ログイン画面のヘルプリンク領域) @Composable fun HelpLink() { }  ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ✅ӳޠ೔ຊαʔόʔͷ૊߹ͤ ✅؆ମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ൟମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ͦͷଞ೔ຊαʔόʔͷ૊߹ͤ ✅೔ຊޠถࠃαʔόʔͷ૊߹ͤ ✅ӳޠถࠃαʔόʔͷ૊߹ͤ ✅؆ମࣈถࠃαʔόʔͷ૊߹ͤ ✅ൟମࣈถࠃαʔόʔͷ૊߹ͤ ✅ͦͷଞถࠃαʔόʔͷ૊߹ͤ ❌  ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜ੒ͨ͠63-Λ $ISPNFʹ౉͠ɺϔϧϓϖʔδΛ։͘  RED 6*ύʔπ͝ͱ5%%͠΍͍͢🙌
  10.   ઀ଓઌ৘ใͱ୺຤ͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ੒͢Δ ✅೔ຊޠ೔ຊαʔόʔͷ૊߹ͤ ✅ӳޠ೔ຊαʔόʔͷ૊߹ͤ ✅؆ମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ൟମࣈ೔ຊαʔόʔͷ૊߹ͤ ✅ͦͷଞ೔ຊαʔόʔͷ૊߹ͤ ✅೔ຊޠถࠃαʔόʔͷ૊߹ͤ ✅ӳޠถࠃαʔόʔͷ૊߹ͤ

    ✅؆ମࣈถࠃαʔόʔͷ૊߹ͤ ✅ൟମࣈถࠃαʔόʔͷ૊߹ͤ ✅ͦͷଞถࠃαʔόʔͷ૊߹ͤ ✅  ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜ੒ͨ͠63-Λ $ISPNFʹ౉͠ɺϔϧϓϖʔδΛ։͘ GREEN private val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().start().resume().get() // テスト @Test fun `ログイン画面でヘルプリンクが押されると、生成したURLをChromeに渡し、ヘルプページを開く`() { // Given (前提条件: ログイン画面を開いている) composeRule.setContent { HelpLink() } // When (操作:ヘルプリンクを押す) composeRule.onNodeWithTag(testTag = "help_link") .assertIsDisplayed() .performClick() // Then (結果:ChromeがURLを開く) val openedActivity = Shadows.shadowOf(activity).nextStartedActivity val openedPackage = openedActivity.getPackage() Truth.assertThat(openedPackage).isEqualTo("com.android.chrome") val openedUrlString = openedActivity.data.toString() Truth.assertThat(openedUrlString).isEqualTo(helpLinkUrl) } // 実装(ログイン画面のヘルプリンク領域) @Composable fun HelpLink() { Box( modifier = Modifier .defaultMinSize(minHeight = 48.dp) .testTag("help_link") .clickable { openHelp(generateHelpLink(language = "ja", country = "JP")) } ) { Text( color = MaterialTheme.colors.helpButtonText, fontSize = 14.sp, text = stringResource(id = R.string.induction_to_open_help) ) } } 👈 ϓϨϏϡʔݟͳ͕Β࣮૷Մ