テストの構造化とリファクタリング

テストの構造化とリファクタリング

June 22, 2023

この記事は、次の動画で解説されている「テストの構造化とリファクタリング」の部分からの引用です。

テストの構造化とリファクタリング #

*言語は Java です。なおこのコードは動画のものから一部改変があります。

テスト対象のコード #

public class FizzBuzz {
  public String convert(int num) {
    if (num % 3 == 0 && num % 5 == 0) {
      return "FizzBuzz";
    }
    if (num % 3 == 0) {
      return "Fizz";
    }
    if (num % 5 == 0) {
      return "Buzz";
    }
    return String.valueOf(num);
  }
}

構造化・リファクタリングの前 #

class FizzBuzzTest {
  private FizzBuzz fizzbuzz;

  @BeforeEach
  void 前準備() {
    fizzbuzz = new FizzBuzz();
  }

  @Test
  void _1を渡すと文字列1を返す() throws Exception {
    assertEquals("1", fizzbuzz.convert(1));
  }

  @Test
  void _2を渡すと文字列2を返す() throws Exception {
    assertEquals("2", fizzbuzz.convert(2));
  }

  @Test
  void _3を渡すと文字列Fizzを返す() throws Exception {
    assertEquals("Fizz", fizzbuzz.convert(3));
  }

  @Test
  void _5を渡すと文字列Buzzを返す() throws Exception {
    assertEquals("Buzz", fizzbuzz.convert(5));
  }

  @Test
  void _15を渡すと文字列FizzBuzzを返す() throws Exception {
    assertEquals("FizzBuzz", fizzbuzz.convert(15));
  }
}

問題点

  • テストコードが「動作する “ドキュメント”」になっていない。
    • テストコードのコメントを見ればコードの具体的な振る舞いは読み取れる。しかし FizzBuzz とはいったい何者で、他の数を渡した場合はどのように動作するべきなのかが伝わらない。
    • 結局 FizzBuzz の実装クラスのコードを見て初めて、3の倍数なら Fizz を、5の倍数なら Buzz を… といったことを理解することができる。
  • 必要最小限なテストになっていない。
    • 「_1 を渡すと文字列 1 を返す」と「_2 を渡すと文字列 2 を返す」のどちらかは無駄。
    • 実はテスト作成者が作業の過程で結果として2つのテストを書いただけだが、それは他人にはわからない。

構造化・リファクタリングの後 #

@DisplayName("Fizz Buzz 数列と変換規則を扱う FizzBuzz クラス")
class FizzBuzzTest {
  private FizzBuzz fizzbuzz;

  @BeforeEach
  void 前準備() {
    fizzbuzz = new FizzBuzz();
  }

  @Nested
  class convertメソッドは数を文字列に変換する {

    @Nested
    class _3の倍数のときは数の代わりにFizzに変換する {
      @Test
      void _3を渡すと文字列Fizzを返す() throws Exception {
        assertEquals("Fizz", fizzbuzz.convert(3));
      }
    }

    @Nested
    class _5の倍数のときは数の代わりにBuzzに変換する {
      @Test
      void _5を渡すと文字列Buzzを返す() throws Exception {
        assertEquals("Buzz", fizzbuzz.convert(5));
      }
    }

    @Nested
    class _3と5の倍数のときは数の代わりにFizzBuzzに変換する {
      @Test
      void _15を渡すと文字列FizzBuzzを返す() throws Exception {
        assertEquals("FizzBuzz", fizzbuzz.convert(15));
      }
    }

    @Nested
    class その他の数のときはそのまま文字列に変換する {
      @Test
      void _1を渡すと文字列1を返す() throws Exception {
        assertEquals("1", fizzbuzz.convert(1));
      }

      // 👇 削除
      // @Test
      // void _2を渡すと文字列2を返す() throws Exception {
      //   assertEquals("2", fizzbuzz.convert(2));
      // }
    }
  }
}

改善点

  • テストコードが「動作する “ドキュメント”」になっている。
    • テストコードのコメントを見れば、実装コードの仕様が伝わるものになった。
  • 必要最小限なテストになっている。
    • テストのメンテナンスコストが最小限になった。