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

TDDチュートリアル

 TDDチュートリアル

社内向けにTDDの説明をする際に用いたスライドです。
Android Studioを使ってTDDをチュートリアル形式で紹介しています。
仮実装、三角測量などといったTDDのテクニックを体験していただけるように設計しております。
また、リズムよくTDDを体験してもらえるように、
テスト開発で使えるショートカットの紹介も交えています。

Hodaka Suzuki

April 10, 2019
Tweet

More Decks by Hodaka Suzuki

Other Decks in Programming

Transcript

  1. 鈴木穂高(Hodaka Suzuki) Twitter @hoddy3190 • 2014年DeNA新卒入社 • アプリゲーム開発・運用(2014/08 〜 2018/10)

    ◦ サーバー、クライアント、マスター管理ツール、インフラ整備、 マネジメントなど • テスト技術チーム - SWET(2018/10 〜) ◦ 仕様品質を向上させるための技術的なアプローチ研究 ▪ https://speakerdeck.com/hoddy3190/xing-shi-shou-fa-nituitediao-betemita ◦ Androidのテスト教育のための活動
  2. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) fizzbuzz_button.setOnClickListener { setResultText(convert(fizzbuzz_form.text.toString())) } } private fun convert(input: String): String { // fizzbuzz変換 return input // 仮 } private fun setResultText(str: String) { result_text.text = str } } 文脈(MainActivityの現在の中身)
  3. • [ ] 数を文字列にして返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [

    ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す
  4. • [ ] 数を文字列にして返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [

    ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す 概要
  5. • [ ] 数を文字列にして返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [

    ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す 枝葉
  6. • [ ] 数を文字列にして返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [

    ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す この順番で実装
  7. • [ ] 数を文字列にして返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [

    ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す この順番で実装 正常系 準正常系 準正常系 準正常系
  8. • [ ] 数を文字列にして返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [

    ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す 対象
  9. 落ちるテストを書く internal class FizzBuzzTest { @Test fun onCreate() { assertEquals(1,

    2) } } assertEqualsと入力すると候補がたくさん出るが 一番上のものを選んでくれればOK(なんでも良い)
  10. 落ちるテストを書く internal class FizzBuzzTest { @Test fun onCreate() { assertEquals(1,

    2) } } usage: assertEquals(expected, actual) expectedが先に来ることに注意
  11. もし が効かない場合 Keymapがコンフリクトしている可能性。 [ Preferences ] -> [ Keymap ]

    でKeymapを変更しよう。 例えば、Ctrl + Shift + Z がよい。 Ctrl + Shift + R 検索ワード
  12. テストは動く仕様書 internal class FizzBuzzTest { @Test fun 数を文字列にして返す () {

    } } 日本語で書くのもあり ※ただし、instrumented testでは使えない
  13. • [ ] 数を文字列にして返す ◦ 1を渡したら文字列"1"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す

    • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す NEW
  14. テストケース修正 internal class FizzBuzzTest { @Test fun `1を渡したら文字列1を返す`() { }

    } バッククオートで囲むのもよい ※ただ、クラス名で適用したときにテスト結果の表示が変になった
  15. 3A internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 // Act 実行 // Assert 検証 } } 前準備、実行、検証
  16. アサートファースト • 一番具体的な例からどういう実装をしなければならないかを逆算して考える • 使う人目線でどんなオブジェクトにどんな関数が生えているとよいのかを考える internal class FizzBuzzTest { @Test

    fun _1を渡したら文字列1を返す() { // Arrange 前準備 // Act 実行 // Assert 検証 assertEquals(“1”, fizzbuzz.convert) } } 数字を文字列に変換する関数が 生えていると使いやすそうだ
  17. アサートファースト • 一番具体的な例からどういう実装をしなければならないかを逆算して考える • 使う人目線でどんなオブジェクトにどんな関数が生えているとよいのかを考える internal class FizzBuzzTest { @Test

    fun _1を渡したら文字列1を返す() { // Arrange 前準備 // Act 実行 // Assert 検証 assertEquals(“1”, fizzbuzz.convert(1)) } } 引数として数字を渡すのはどうだろう
  18. コンパイルエラー直し internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } 初期化処理追加
  19. コンパイルエラー直し internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } まだエラー
  20. クラス作成 エラーが出ている文字の上でたたくと、修正案を 表示してくれる。今回はクラスを作る。 Ctrl + Shift + R internal class

    FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Opt + Enter
  21. クラス作成 エラーが出ている文字の上でたたくと、修正案を 表示してくれる。今回はクラスを作る。 Ctrl + Shift + R internal class

    FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Opt + Enter
  22. クラス作成 エラーが出ている文字の上でたたくと、修正案を 表示してくれる。今回はクラスを作る。 Ctrl + Shift + R internal class

    FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Opt + Enter
  23. クラス作成 エラーが出ている文字の上でたたくと、修正案を 表示してくれる。今回はクラスを作る。 Ctrl + Shift + R internal class

    FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Opt + Enter そのままOK
  24. 前回見ていた場所に戻る internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Cmd + [
  25. 新たなコンパイルエラー internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } 新たなコンパイルエラー
  26. また internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Opt + Enter
  27. メンバ関数ができる class FizzBuzz { fun convert(i: Int): String { return

    "" } } 仮引数はIntにして、返り値はStringにする とりあえず空文字を返しておく
  28. ようやくコンパイルエラーがなくなる internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } }
  29. テスト実行 internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Ctrl + Shift + R
  30. テストが通るように修正 class FizzBuzz { fun convert(i: Int): String { return

    "1" } } テストを通すための最小限の修正 GREEN
  31. テスト実行 internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } } Ctrl + Shift + R
  32. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す • [

    ] 3の倍数のときは数のかわりにFizzと返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す
  33. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す NEW
  34. 入力が2のときのテストを書こう internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) assertEquals("2", fizzbuzz.convert(2)) } }
  35. 入力が2のときのテストを書こう internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { //

    Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) assertEquals("2", fizzbuzz.convert(2)) } }
  36. アサーションルーレットアンチパターン 1つのテストに複数のアサーション internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() {

    // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) assertEquals("2", fizzbuzz.convert(2)) } } JUnitの場合、あるアサーションでエラーが出て しまうと、以降のアサーションが実行されず、 TDDサイクルを正常に回すことができなくなる
  37. internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // Arrange

    前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列2を返す() { // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("2", fizzbuzz.convert(2)) } }
  38. internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // Arrange

    前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列2を返す() { // Arrange 前準備 val fizzbuzz = FizzBuzz() // Act 実行 // Assert 検証 assertEquals("2", fizzbuzz.convert(2)) } } テストを分割して書く
  39. テストコード internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { val

    fizzbuzz = FizzBuzz() assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列2を返す() { val fizzbuzz = FizzBuzz() assertEquals("2", fizzbuzz.convert(2)) } } Refactoring
  40. internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { val fizzbuzz

    = FizzBuzz() assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列2を返す() { val fizzbuzz = FizzBuzz() assertEquals("2", fizzbuzz.convert(2)) } } 3Aコメントの削除 テストコード Refactoring 3Aコメントの削除
  41. @Test fun _1を渡したら文字列1を返す() { val fizzbuzz = FizzBuzz() assertEquals("1", fizzbuzz.convert(1))

    } @Test fun _2を渡したら文字列2を返す() { val fizzbuzz = FizzBuzz() assertEquals("2", fizzbuzz.convert(2)) } テストコード Refactoring
  42. @Test fun _1を渡したら文字列1を返す() { val fizzbuzz = FizzBuzz() assertEquals("1", fizzbuzz.convert(1))

    } @Test fun _2を渡したら文字列2を返す() { val fizzbuzz = FizzBuzz() assertEquals("2", fizzbuzz.convert(2)) } テストコード Refactoring 共通化できるところを 共通化する
  43. internal class FizzBuzzTest { @BeforeEach internal fun setUp() { TODO("not

    implemented") // To change body of created functions use File | Settings | File Templates. } @Test fun _1を渡したら文字列1を返す() { val fizzbuzz = FizzBuzz() assertEquals("1", fizzbuzz.convert(1)) } ... setUp関数作成 Refactoring
  44. internal class FizzBuzzTest { private lateinit var fizzbuzz: FizzBuzz @BeforeEach

    internal fun setUp() { fizzbuzz = FizzBuzz() } @Test fun _1を渡したら文字列1を返す() { val fizzbuzz = FizzBuzz() assertEquals("1", fizzbuzz.convert(1)) } ... 共通処理をsetUp関数に集約 Refactoring 追加 追加
  45. @Test fun _1を渡したら文字列1を返す() { val fizzbuzz = FizzBuzz() assertEquals("1", fizzbuzz.convert(1))

    } @Test fun _2を渡したら文字列2を返す() { val fizzbuzz = FizzBuzz() assertEquals("2", fizzbuzz.convert(2)) } 少しずつ直してテスト実行 Refactoring まずはここだけ削除
  46. @Test fun _1を渡したら文字列1を返す() { assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列2を返す()

    { val fizzbuzz = FizzBuzz() assertEquals("2", fizzbuzz.convert(2)) } 少しずつ直してテスト実行 Refactoring 次はここを削除
  47. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す
  48. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す 対象 仮実装・三角測量を使いながらやってみよう
  49. class FizzBuzz { fun convert(i: Int): String { return i.toString()

    } } internal class FizzBuzzTest { private lateinit var fizzbuzz: FizzBuzz @BeforeEach internal fun setUp() { fizzbuzz = FizzBuzz() } @Test fun _1を渡したら文字列 1を返す() { assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列 2を返す() { assertEquals("2", fizzbuzz.convert(2)) } } 今段階のテストコード 今段階のプロダクトコード
  50. 便利なショートカット一覧(keymap種別: Mac OS X 10.5+) Ctrl + Shift + R

    Cmd + N Opt + Enter Cmd + Shift + T Ctrl + R コードを生成(テスト関数などを作れる) テストファイルを作る テストファイルとプロダクトファイルの行き来をする クイック修正 テストの個別実行 最後に実行したテストの再実行 前見ていた場所に戻る Generate... Test Run ‘hogeTest‘ Run ‘hogeTest‘ コマンド アクション名 説明 Show Intention Actions Back Cmd + [
  51. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す NEW
  52. internal class FizzBuzzTest { @Test fun _1を渡したら文字列1を返す() { // 省略

    } @Test fun _2を渡したら文字列2を返す() { // 省略 } @Test fun _3を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(3)) } } 追加
  53. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す ◦ [ ] 6を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す NEW
  54. 三角測量 // 省略 @Test fun _3を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(3)) }

    @Test fun _6を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(6)) } // 省略 追加
  55. テストが通るように修正 class FizzBuzz { fun convert(i: Int): String { if

    (i % 3 == 0) return "Fizz" return i.toString() } } GREEN
  56. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す ◦ [ ] 6を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す
  57. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す ◦ [ ] 6を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す 対象
  58. ... @Test fun _6を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(6)) } @Test fun

    _5を渡したら文字列Buzzを返す() { assertEquals("Buzz", fizzbuzz.convert(5)) } ... テストケース追加 追加
  59. テストが通るように修正 class FizzBuzz { fun convert(i: Int): String { if

    (i % 3 == 0) return "Fizz" if (i % 5 == 0) return "Buzz" return i.toString() } }
  60. テストが通るように修正 class FizzBuzz { fun convert(i: Int): String { if

    (i % 3 == 0) return "Fizz" if (i % 5 == 0) return "Buzz" return i.toString() } } あえてif (i == 5) とは 書かなかった
  61. FizzBuzzを一切知らない人がこのテストコードを見たら、 FizzBuzzの挙動がわかるようになっているか @Test fun _1を渡したら文字列1を返す() { assertEquals("1", fizzbuzz.convert(1)) } @Test

    fun _2を渡したら文字列2を返す() { assertEquals("2", fizzbuzz.convert(2)) } @Test fun _3を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(3)) } @Test fun _6を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(6)) } @Test fun _5を渡したら文字列Buzzを返す() { assertEquals("Buzz", fizzbuzz.convert(5)) }
  62. テストを構造化 @Nested inner class _3の倍数の場合 { } @Nested inner class

    _5の倍数の場合 { } @Nested inner class その他の場合 { }
  63. @Nested inner class _3の倍数の場合 { @Test fun _3を渡したら文字列Fizzを返す() { assertEquals("Fizz",

    fizzbuzz.convert(3)) } @Test fun _6を渡したら文字列Fizzを返す() { assertEquals("Fizz", fizzbuzz.convert(6)) } } @Nested inner class _5の倍数の場合 { @Test fun _5を渡したら文字列Buzzを返す() { assertEquals("Buzz", fizzbuzz.convert(5)) } } @Nested inner class その他の場合 { @Test fun _1を渡したら文字列1を返す() { assertEquals("1", fizzbuzz.convert(1)) } @Test fun _2を渡したら文字列2を返す() { assertEquals("2", fizzbuzz.convert(2)) } }
  64. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す ◦ [ ] 6を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す ◦ [ ] 5を渡したら文字列"Buzz"を返す ◦ [ ] 10を渡したら文字列"Buzz"を返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す
  65. まとめ • 問題を小さく分割する ◦ TODOリスト形式など • 歩幅を適切に選ぶ ◦ 不安だったら テスト

    -> 仮実装 -> 三角測量 -> 実装 ◦ やや不安だったら テスト -> 仮実装 -> 実装 ◦ 不安がなければ テスト -> 明白な実装 • テストコードの読み手のことも考えて テストの構造化とリファクタリングも忘れずに
  66. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す ◦ [ ] 5を渡したら文字列"Buzz"を返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す 対象
  67. • [ ] 数を文字列にして返す ◦ [ ] 1を渡したら文字列"1"を返す ◦ [

    ] 2を渡したら文字列"2"を返す • [ ] 3の倍数のときは数のかわりにFizzと返す ◦ [ ] 3を渡したら文字列"Fizz"を返す ◦ [ ] 6を渡したら文字列"Fizz"を返す • [ ] 5の倍数のときはBuzzと返す ◦ [ ] 5を渡したら文字列"Buzz"を返す ◦ [ ] 10を渡したら文字列"Buzz"を返す • [ ] 3と5両方の倍数の場合にはFizzBuzzと返す ◦ [ ] 15を渡したら文字列"FizzBuzz"を返す ◦ [ ] 30を渡したら文字列"FizzBuzz"を返す
  68. @BeforeEach fun setup() { fizzbuzz = FizzBuzzComp() } @Nested inner

    class _3の倍数かつ5の倍数の場合 { @Test fun _15を渡したら文字列FizzBuzzを返す() { assertEquals("FizzBuzz", fizzbuzz.convert(15)) } } @Nested inner class _3の倍数の場合 { // 省略 }
  69. テストが通るように修正 class FizzBuzz { fun convert(i: Int): String { if

    (num % 15 == 0) return "FizzBuzz" if (num % 3 == 0) return "Fizz" if (num % 5 == 0) return "Buzz" return i.toString() } }
  70. @Nested inner class _3の倍数かつ5の倍数の場合 { @Test fun _15を渡したら文字列FizzBuzzを返す() { assertEquals("FizzBuzz",

    fizzbuzz.convert(15)) } @Test fun _30を渡したら文字列FizzBuzzを返す() { assertEquals("FizzBuzz", fizzbuzz.convert(30)) } } @Nested inner class _3の倍数の場合 { // 省略 } 不安だったので追加
  71. @Nested inner class _3の倍数または5の倍数の場合 { @Nested inner class _3の倍数または5の倍数の場合 {

    @Test fun _15を渡したら文字列 FizzBuzzを返す() { assertEquals("FizzBuzz", fizzbuzz.convert(15)) } @Test fun _30を渡したら文字列 FizzBuzzを返す() { assertEquals("FizzBuzz", fizzbuzz.convert(30)) } } @Nested inner class _3の倍数の場合 { // 省略 } @Nested inner class _5の倍数の場合 { // 省略 } } @Nested inner class その他の場合 { // 省略 } Refactoring
  72. @Nested inner class _3の倍数または5の倍数の場合 { @Nested inner class _3の倍数または5の倍数の場合 {

    @Test fun _15を渡したら文字列 FizzBuzzを返す() { assertEquals("FizzBuzz", fizzbuzz.convert(15)) } @Test fun _30を渡したら文字列 FizzBuzzを返す() { assertEquals("FizzBuzz", fizzbuzz.convert(30)) } } @Nested inner class _3の倍数の場合 { // 省略 } @Nested inner class _5の倍数の場合 { // 省略 } } @Nested inner class その他の場合 { // 省略 } Refactoring 構造化