Share
Embed
KotlinとAndroidではじめるテスト駆動開発
Androidアプリのテスト駆動開発入門
https://speakerdeck.com/tonionagauzzi/androidapurinotesutoqu-dong-kai-fa-ru-men
の第2版。差分は以下。
1. Jetpack Composeを用いたTDDの実例を追加
2. p23の文字化けなど細かい不具合を修正
tonionagauzzi
July 08, 2024
Transcript
40 slides
-
5%%ಋೖͷഎܠ • 5%%Ͱ࡞Δͱ oςετ͘͢͠ͳΔΒ͍͠ʂ oίʔυ៉ྷʹͳΔΒ͍͠ʂ oґଘϞδϡʔϧ ϨΨγʔίʔυ গͣͭ͠ςετͰ͖ͦ͏ʂ 5FTUBCMF
.PEVMF -FHBDZ .PEVMF ςετର -
ϩάΠϯը໘ʹϔϧϓϦϯΫΛՃ • ϢʔβʔετʔϦʔ o ϩάΠϯૢ࡞Λߦ͏Ϣʔβʔͱͯ͠ɺϩάΠϯૢ࡞ʹͭ·͍ͣͨͱ͖ɺ ͦͷը໘্͔Βؔ࿈͢ΔϔϧϓϖʔδΛ։͖͍ͨɻ o ͳͥͳΒɺٙͷ͑ΛҰ͔Β୳͢खؒΛ͔͚ͣʹ ؔ࿈͢ΔϔϧϓϖʔδΛࢀর͠ɺϩάΠϯΛྃͤ͞ɺ ຊདྷΓ͍ͨۀΛਐΊΒΕΔ͔Βͩɻ
o • ड͚ೖΕ݅ o ϩάΠϯը໘͔ΒϔϧϓϖʔδΛ։͚Δ͜ͱ o ͷݴޠઃఆʹԠͯ͡ϔϧϓͷݴޠΛม͑Δ͜ͱ o ଓઌ͝ͱʹఏڙαʔϏε͕ҟͳΔͨΊɺଓઌʹԠͯ͡ϔϧϓͷ༰Λม͑Δ͜ͱ -
ଓઌใͱͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ͢Δ ☑ຊޠຊαʔόʔͷ߹ͤ ☑ӳޠຊαʔόʔͷ߹ͤ ☑؆ମࣈຊαʔόʔͷ߹ͤ ☑ൟମࣈຊαʔόʔͷ߹ͤ ☑ͦͷଞຊαʔόʔͷ߹ͤ ☑ຊޠถࠃαʔόʔͷ߹ͤ ☑ӳޠถࠃαʔόʔͷ߹ͤ ☑؆ମࣈถࠃαʔόʔͷ߹ͤ
☑ൟମࣈถࠃαʔόʔͷ߹ͤ ☑ͦͷଞถࠃαʔόʔͷ߹ͤ ☑ଓઌΛఆͰ͖ͳ͔ͬͨ߹ɺຊޠຊαʔόʔͷ߹ͤͱͳΔ ☑ ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜͨ͠63-Λ $ISPNFʹ͠ɺϔϧϓϖʔδΛ։͘ CHECK LIST -
// テスト @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 -
// テスト @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 -
// テスト @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 -
// テスト @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 -
// テスト @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 -
ଓઌใͱͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ͢Δ ✅ຊޠຊαʔόʔͷ߹ͤ ✅ӳޠຊαʔόʔͷ߹ͤ ✅؆ମࣈຊαʔόʔͷ߹ͤ ✅ൟମࣈຊαʔόʔͷ߹ͤ ✅ͦͷଞຊαʔόʔͷ߹ͤ ✅ຊޠถࠃαʔόʔͷ߹ͤ ✅ӳޠถࠃαʔόʔͷ߹ͤ ✅؆ମࣈถࠃαʔόʔͷ߹ͤ
✅ൟମࣈถࠃαʔόʔͷ߹ͤ ✅ͦͷଞถࠃαʔόʔͷ߹ͤ ☑ ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜͨ͠63-Λ $ISPNFʹ͠ɺϔϧϓϖʔδΛ։͘ -
ଓઌใͱͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ63-Λੜ͢Δ ✅ຊޠຊαʔόʔͷ߹ͤ ✅ӳޠຊαʔόʔͷ߹ͤ ✅؆ମࣈຊαʔόʔͷ߹ͤ ✅ൟମࣈຊαʔόʔͷ߹ͤ ✅ͦͷଞຊαʔόʔͷ߹ͤ ✅ຊޠถࠃαʔόʔͷ߹ͤ ✅ӳޠถࠃαʔόʔͷ߹ͤ ✅؆ମࣈถࠃαʔόʔͷ߹ͤ
✅ൟମࣈถࠃαʔόʔͷ߹ͤ ✅ͦͷଞถࠃαʔόʔͷ߹ͤ ☑ ϩάΠϯը໘ͰϔϧϓϦϯΫ͕ԡ͞ΕΔͱɺͰੜͨ͠63-Λ $ISPNFʹ͠ɺϔϧϓϖʔδΛ։͘ -
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 -
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 -
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%%͍͢͠🙌 -
ଓઌใͱͷݴޠઃఆʹԠͨ͡ϔϧϓϖʔδͷ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) ) } } 👈 ϓϨϏϡʔݟͳ͕Β࣮Մ