8

私は最近、従来の FizzBu​​zz ソリューションの作成を依頼されたインタビューを受けました。

1 から 100 までの数字のリストを出力します。

  • 3 と 5 のすべての倍数について、数字は「FizzBu​​zz」に置き換えられます。
  • 残りの 3 の倍数はすべて「Fizz」に置き換えられます。
  • 残りの 5 の倍数はすべて「バズ」に置き換えられます。

私のソリューションは役割のために Java で書かれていましたが、これは必須ではありませんでした。インタビュアーはTDDのいくつかの証拠を知りたがっていたので、その精神で、私は独自のFizzBu​​zz単体テストを作成することにしました:

public class FizzBuzzTest {

    @Test
    public void testReturnsAnArrayOfOneHundred() {
        String[] result = FizzBuzz.getResultAsArray();
        assertEquals(100, result.length);
    }

    @Test
    public void testPrintsAStringRepresentationOfTheArray() {
        String result = FizzBuzz.getResultAsString();
        assertNotNull(result);
        assertNotSame(0, result.length());
        assertEquals("1, 2", result.substring(0, 4));
    }

    @Test
    public void testMultiplesOfThreeAndFivePrintFizzBuzz() {
        String[] result = FizzBuzz.getResultAsArray();

        // Check all instances of "FizzBuzz" in array
        for (int i = 1; i <= 100; i++) {
            if ((i % 3) == 0 && (i % 5) == 0) {
                assertEquals("FizzBuzz", result[i - 1]);
            }
        }
    }

    @Test
    public void testMultiplesOfThreeOnlyPrintFizz() {
        String[] result = FizzBuzz.getResultAsArray();

        // Check all instances of "Fizz" in array
        for (int i = 1; i <= 100; i++) {
            if ((i % 3) == 0 && !((i % 5) == 0)) {
                assertEquals("Fizz", result[i - 1]);
            }
        }
    }

    @Test
    public void testMultiplesOfFiveOnlyPrintBuzz() {
        String[] result = FizzBuzz.getResultAsArray();

        // Check all instances of "Buzz" in array
        for (int i = 1; i <= 100; i++) {
            if ((i % 5) == 0 && !((i % 3) == 0)) {
                assertEquals("Buzz", result[i - 1]);
            }
        }
    }
}

私の結果の実装は次のようになりました。

public class FizzBuzz {

    private static final int MIN_VALUE = 1;
    private static final int MAX_VALUE = 100;


    private static String[] generate() {
        List<String> items = new ArrayList<String>();

        for (int i = MIN_VALUE; i <= MAX_VALUE; i++) {

            boolean multipleOfThree = ((i % 3) == 0);
            boolean multipleOfFive = ((i % 5) == 0);

            if (multipleOfThree && multipleOfFive) {
                items.add("FizzBuzz");
            }
            else if (multipleOfThree) {
                items.add("Fizz");
            }
            else if (multipleOfFive) {
                items.add("Buzz");
            }
            else {
                items.add(String.valueOf(i));
            }
        }

        return items.toArray(new String[0]);
    }

    public static String[] getResultAsArray() {
        return generate();
    }

    public static String getResultAsString() {
        String[] result = generate();
        String output = "";
        if (result.length > 0) {
            output = Arrays.toString(result);
            // Strip out the brackets from the result
            output = output.substring(1, output.length() - 1);
        }
        return output;
    }

    public static final void main(String[] args) {
        System.out.println(getResultAsString());
    }
}

ソリューション全体で、ある夜遅くに約 20 分かかりました。コードを提出する前に、必要以上に長い間コードを神経質にチェックすることも含まれていました :)

私が最初に提出したものを見直してください: オーバーエンジニアリングを避けるために、早い段階で「倍数」の計算を generate() メソッドにマージすることに決めましたが、今では間違いだったと思います。また、別個の getResultAsArray/generate メソッドは明らかに OTT でした。getResultAsString は、一方が他方に委譲するだけなので、main() メソッドとマージすることもできます。

私はまだTDDにかなり慣れていないので、この場合はがっかりしたかもしれません。特にTDDの実践に関して、このアプローチを改善できる他の方法を探していますか?


アップデート

以下の非常に有用な提案に基づいて、より「TDD に適した」ものであると考えられるものへの回答を作り直しました。

変更点:

  • FizzBu​​zz ロジックを出力生成から分離して、ソリューションをよりスケーラブルにする

  • テストを簡素化するために、テストごとに 1 つのアサーションのみ

  • いずれの場合も、ロジックの最も基本的なユニットのみをテストする

  • ストリング構築を確認する最終テストも検証済み

コード:

public class FizzBuzzTest {

    @Test
    public void testMultipleOfThreeAndFivePrintsFizzBuzz() {
        assertEquals("FizzBuzz", FizzBuzz.getResult(15));
    }

    @Test
    public void testMultipleOfThreeOnlyPrintsFizz() {
        assertEquals("Fizz", FizzBuzz.getResult(93));
    }

    @Test
    public void testMultipleOfFiveOnlyPrintsBuzz() {
        assertEquals("Buzz", FizzBuzz.getResult(10));
    }

    @Test
    public void testInputOfEightPrintsTheNumber() {
        assertEquals("8", FizzBuzz.getResult(8));
    }

    @Test
    public void testOutputOfProgramIsANonEmptyString() {
        String out = FizzBuzz.buildOutput();
        assertNotNull(out);
        assertNotSame(0, out.length());
    }
}

public class FizzBuzz {

    private static final int MIN_VALUE = 1;
    private static final int MAX_VALUE = 100;

    public static String getResult(int input) {
        boolean multipleOfThree = ((input % 3) == 0);
        boolean multipleOfFive = ((input % 5) == 0);

        if (multipleOfThree && multipleOfFive) {
            return "FizzBuzz";
        }
        else if (multipleOfThree) {
            return "Fizz";
        }
        else if (multipleOfFive) {
            return "Buzz";
        }
        return String.valueOf(input);
    }

    public static String buildOutput() {
        StringBuilder output = new StringBuilder();

        for (int i = MIN_VALUE; i <= MAX_VALUE; i++) {
            output.append(getResult(i));

            if (i < MAX_VALUE) {
                output.append(", ");
            }
        }

        return output.toString();
    }

    public static final void main(String[] args) {
        System.out.println(buildOutput());
    }
}
4

3 に答える 3

6

TDDがXPやアジャイルの哲学と強く結びついているのには理由があります。それは私たちをテスト可能なコードの小さな単位に駆り立てます。したがって、TheSimplestThingWhichCouldPossiblyWorkや単一責任原則などの概念はテスト駆動アプローチから外れます。

それはあなたのシナリオでは明らかに起こっていません。あなたはFizzBu​​zzビットではなく、数字の配列に固執しました(手がかりは本当に問題になっています)。

明らかに、あなたは完全に人工的な状況にあり、TDDを偽造するのは難しいです。しかし、私は「実際の」TDDコードが翻訳メソッドを公開していることを期待しています。これの何か:

@Test     
public void testOtherNumber() {        
     String result = FizzBuzz.translateNumber(23);
     assertEquals("23", result);
 } 

@Test     
public void testMultipleOfThree() {        
     String result = FizzBuzz.translateNumber(3);
     assertEquals("Fizz", result);
 } 

@Test     
public void testMultipleOfFive() {        
     String result = FizzBuzz.translateNumber(25);
     assertEquals("Buzz", result);
 } 

@Test     
public void testMultipleOfFifteen() {        
     String result = FizzBuzz.translateNumber(45);
     assertEquals("FizzBuzz", result);
 } 

重要なのは、それらのそれぞれが明確な結果を生み出し、失敗したテストから始めるのが簡単であるということです。

FizzBu​​zzビットを実行した後、配列の処理を実行するのは簡単です。それに関する重要なポイントは、ハードコーディングを回避することです。最初は完全な実装が必要ない場合があります。比較的少数の要素、たとえば15を生成するだけで十分です。これには、より優れた設計を作成できるという利点があります。結局のところ、インタビュアーが戻ってきて「実際には121個の要素の配列が必要です」と言った場合、コードのどのくらいを変更する必要がありますか?テストはいくつですか?


TDDの課題の1つは、どこから始めればよいかを知ることです。Gojko Adzicは、囲碁のゲームを実装するCoding Dojoについて説明し、これについて示唆に富む記事を書きました。


「私の翻訳方法を公開することで、後でカプセル化の理由で私に不利になる可能性はありますか?」

TDDで最も熱く議論されているトピックの1つ。考えられる答えは次のとおりです。

  1. メソッドをプライベートに保ち、ユニットテストをクラスに埋め込みます。
  2. パブリックメソッドに対してテストを記述し、テストに合格したメソッドをプライベートにして、テストをリファクタリングします。
  3. 上記のバリエーション:条件付きコンパイル(または同様のもの)を使用して、メソッドを公開または非表示にします。
  4. それらを公開しておくだけです

正しい答えはありません。多くの場合、特定の要件や個人的な気まぐれに依存します。たとえば、FizzBu​​zz自体は些細なことですが、データを取得し、ビジネスルールを適用して、検証結果を返すコードを作成する必要があることがよくあります。ルールを単一のデータ項目に適用する必要がある場合もあれば、レコードセット全体に対して適用する必要がある場合もあれば、どちらかに対して適用する必要がある場合もあります。

したがって、両方のメソッドを公開するAPIは必ずしも間違っているわけではありません。そして確かにインタビューの状況では、APIデザインのニュアンスについて話し合う機会が与えられます。これは会話の良いトピックです。

于 2012-03-06T13:19:19.343 に答える
2

FizzBu​​zz パズルには 2 つの部分があります。ループと、指定された int の正しい文字列の生成です。伝統的に、人々は 2 つを 1 つの関数に結合します (非常に単純なので、これは完全に合理的です) が、TDD の場合、2 番目の部分を除外して、個別にテストできるようにします。擬似コード:

String[] fizzbuzz(int count)
    for i: 0 ... count:
        line = fizzOrBuzz(i)
        output.add(line)

fizzOrBuzzこれで、ループに行かなくてもメソッドをテストでき、メソッドが機能することを確認したら、ループをテストできます。可能性のあるエッジ ケース (0、-1、Integer.MAX_VALUE) に到達するようにしてください。

FizzBu​​zz のような単純なものについては、ここで締めくくります。モック化された FizzBu​​zzer などは作成しません。ただし、その決定を擁護する準備をしてください (基本的には、関数の単純さは非常に複雑なテストを保証しないと言ってください)。私が人々にインタビューするとき、私は彼らのアイデアに対するあまり良くない反例を提案し、彼らが彼らのアイデアを擁護できるかどうか (またはおそらくそれを改善できるか!) を見るのが好きです。

于 2012-03-06T13:35:11.900 に答える
1

私はTDDの素晴らしい経験を主張するつもりはないので、私が権威として話しているとは思わないでください!それを念頭に置いて、これが私の$0.02です。

  1. これらすべての静的メソッドをインスタンスメソッドにしてから、FizzBuzzテストごとに新しいインスタンスを作成します。
  2. を取り除いて、generate()そのコードをに入れてgetResultAsArray()ください。(これは非常にマイナーです。)
  3. ユニットテストクラスに定数を含めることは問題ありません。(つまり、@ APCが言ったこと)。

あなたが言及する他の可能な変更は、私にはやり過ぎのように思えます。

もう1つのポイント:FizzBu​​zz?えっ!それはとても些細なことなので、使用するのは非常に貧弱な例の質問です...

于 2012-03-06T13:32:37.897 に答える