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

良いユニットテストを書こう

mototakatsu
December 20, 2024

 良いユニットテストを書こう

Ebisu.mobile #8 登壇資料です。
https://hey.connpass.com/event/335971/

mototakatsu

December 20, 2024
Tweet

Other Decks in Programming

Transcript

  1. © asken.inc 2 自己紹介 株式会社asken Android Engineer / QA Engineer

    Android Engineerとしてasken入社 Android開発は2012年から 元々テストが好きだったこともあって、 2024年からQA Engineer JSTQB FL保有 趣味はボードゲームと漫画とラジオ
  2. © asken.inc 9 (前提)MVVM アーキテクチャを採用 Model, ViewModel(UiModel含む): ユニットテストを書いている View: ユニットテストを書いていない

    コンポーネントテストを検討中 ユニットテストを書いているところ View ViewModel UiModel Model
  3. © asken.inc 12 テストの結果が信頼できる プロダクトコード: 正しい プロダクトコード: 誤り テスト結果: 成功

    期待どおり 偽陰性 (検知漏れ) テスト結果: 失敗 偽陽性 (誤検知) 期待どおり 偽陽性、偽陰性のどちらも発生しない テストの結果が信頼できないと、 障害修正のコストが跳ね上がる!
  4. © asken.inc 14 読みにくいテストコード例 @Test fun validate_birthday() { val today

    = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) // 14歳の場合 (誕生日前日) は NG … assertFalse(... } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime {
  5. © asken.inc 15 NG: 1つのテストケースで複数テストしている @Test fun validate_birthday() { val

    today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) // 14歳の場合 (誕生日前日) は NG … assertFalse(... } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { 複数の確認項目が1つのテストケースにある
  6. © asken.inc 16 OK: 1つのテストケースで1つの観点をテストする @Test fun validate_birthday_正常系() { val

    today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) } @Test fun validate_birthday_異常系() { ... 確認項目ごとにテストケースを分割
  7. © asken.inc 17 NG: 何を確認したいのかわからないテストケース名 @Test fun validate_birthday_正常系() { val

    today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { ... 正常って何? 何がどうなっていれば正しい? テスト対象の関数名が テストケース名に含まれている
  8. © asken.inc 18 OK: テストケース名でテストの意図がわかる @Test fun 15歳の誕生日当日は登録可能とする() { val

    today = LocalDateTime.now() val birthday = getDateBefore15Years(today) assertTrue(Birthday.validate(birthday, today)) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { ...
  9. © asken.inc 19 NG: テストケースの処理フローの説明がない @Test fun 15歳の誕生日当日は登録可能とする() { val

    today = LocalDateTime.now() val birthday = getDateBefore15Years(today) assertTrue(Birthday.validate(birthday, today)) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { ... AAAパターンのコメントを追加して テストの流れを整理する
  10. © asken.inc 20 OK: AAAパターンで処理フローを整理する @Test fun 15歳の誕生日当日は登録可能とする() { //

    Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { Arrange / Act / Assert に分割して整理する
  11. © asken.inc 21 NG: テストデータ作成を関数化している @Test fun 15歳の誕生日当日は登録可能とする() { //

    Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { 他のテストケースでも同じ処理を使って いるので関数化している
  12. © asken.inc 22 OK: 上から下へ素直に読み下せるようにする @Test fun 15歳の誕生日当日は登録可能とする() { //

    Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = today.minusYears(15) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } 他のテストケースと同じ処理であっても 関数の切り出しをしない
  13. © asken.inc 23 NG: テストデータを計算している @Test fun 15歳の誕生日当日は登録可能とする() { //

    Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = today.minusYears(15) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } システム時刻から今日が15歳の誕生日とな る日付を計算している
  14. © asken.inc 24 OK: テストデータに必要な値をベタ書きする @Test fun 15歳の誕生日当日は登録可能とする() { //

    Arrange // 今日と15歳の誕生日を用意する val today = SimpleDateFormat("yyyyMMdd").parse("20241201") val birthday = SimpleDateFormat("yyyyMMdd").parse("20091201") // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } “今日”の日付と、その日が15歳の誕生日と なる日をベタ書きする