複雑なメソッドを持つかなり複雑なオブジェクトがあり、テストとそれをパスするための最低限のものを書いた場合(最初に失敗した後、赤)。いつ戻って実際のコードを書きますか?そして、再テストする前に実際のコードをどれだけ書くのでしょうか?最後の方がより直感的だと思います。
「戻って」「実際のコード」を書くことはありません。それはすべて実際のコードです。あなたがすることは戻って、新しいテストに合格するためにコードを変更することを強制する別のテストを追加することです。
再テストする前にどのくらいのコードを書きますか?無し。あなたは書き込みゼロ失敗テストせずにコードを強制的にあなたがより多くのコードを書くこと。
パターンに注目してください。
それが役立つことを期待して、(別の)簡単な例を見ていきましょう。
Assert.Equal("1", FizzBuzz(1));
簡単です。
public String FizzBuzz(int n) {
return 1.ToString();
}
あなたが本当のコードと呼ぶものではありませんか?変更を強制するテストを追加しましょう。
Assert.Equal("2", FizzBuzz(2));
のような愚かなことをすることもできif n == 1
ますが、正気の解決策にスキップします。
public String FizzBuzz(int n) {
return n.ToString();
}
クール。これは、FizzBuzz以外のすべての番号で機能します。量産コードの変更を強制する次の入力は何ですか?
Assert.Equal("Fizz", FizzBuzz(3));
public String FizzBuzz(int n) {
if (n == 3)
return "Fizz";
return n.ToString();
}
そしてまた。まだ合格しないテストを作成します。
Assert.Equal("Fizz", FizzBuzz(6));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
return n.ToString();
}
そして、3の倍数すべてをカバーしました(5の倍数ではありません。注意して戻ってきます)。
「バズ」のテストはまだ書いていないので、書きましょう。
Assert.Equal("Buzz", FizzBuzz(5));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
if (n == 5)
return "Buzz"
return n.ToString();
}
繰り返しますが、別のケースを処理する必要があることを知っています。
Assert.Equal("Buzz", FizzBuzz(10));
public String FizzBuzz(int n) {
if (n % 3 == 0)
return "Fizz";
if (n % 5 == 0)
return "Buzz"
return n.ToString();
}
そして、3の倍数ではない5のすべての倍数を処理できるようになりました。
この時点まで、リファクタリング手順を無視していましたが、重複が見られます。今それをきれいにしましょう。
private bool isDivisibleBy(int divisor, int input) {
return (input % divisor == 0);
}
public String FizzBuzz(int n) {
if (isDivisibleBy(3, n))
return "Fizz";
if (isDivisibleBy(5, n))
return "Buzz"
return n.ToString();
}
クール。これで重複を削除し、適切な名前の関数を作成しました。コードを変更せざるを得ない次のテストは何ですか?さて、3と5の両方で数値が割り切れる場合を回避してきました。今から書きましょう。
Assert.Equal("FizzBuzz", FizzBuzz(15));
public String FizzBuzz(int n) {
if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
return "FizzBuzz";
if (isDivisibleBy(3, n))
return "Fizz";
if (isDivisibleBy(5, n))
return "Buzz"
return n.ToString();
}
テストは合格しましたが、さらに重複しています。オプションがありますが、「ローカル変数の抽出」を数回適用して、リライトではなくリファクタリングします。
public String FizzBuzz(int n) {
var isDivisibleBy3 = isDivisibleBy(3, n);
var isDivisibleBy5 = isDivisibleBy(5, n);
if ( isDivisibleBy3 && isDivisibleBy5 )
return "FizzBuzz";
if ( isDivisibleBy3 )
return "Fizz";
if ( isDivisibleBy5 )
return "Buzz"
return n.ToString();
}
また、妥当な入力はすべて網羅しましたが、不合理な入力はどうでしょうか?0または負の値を渡すとどうなりますか?それらのテストケースを作成します。
public String FizzBuzz(int n) {
if (n < 1)
throw new InvalidArgException("n must be >= 1);
var isDivisibleBy3 = isDivisibleBy(3, n);
var isDivisibleBy5 = isDivisibleBy(5, n);
if ( isDivisibleBy3 && isDivisibleBy5 )
return "FizzBuzz";
if ( isDivisibleBy3 )
return "Fizz";
if ( isDivisibleBy5 )
return "Buzz"
return n.ToString();
}
これはまだ「実際のコード」のように見え始めていますか?さらに重要なことは、どの時点で「非現実的なコード」でなくなり、「実際の」コードに移行したのでしょうか?それは熟考するものです...
そのため、各ステップでパスしないとわかっているテストを探すだけでこれを行うことができましたが、多くの練習を重ねてきました。私が仕事をしているとき、物事はこれほど単純ではなく、どのテストが変更を強制するかを常に知っているとは限りません。ときどきテストを書いて、それがすでに合格するのを見て驚くでしょう!始める前に「テストリスト」を作成する習慣を身に付けることを強くお勧めします。このテストリストには、考えられるすべての「興味深い」入力が含まれている必要があります。これらすべてを使用するわけではない可能性があり、ケースを追加する可能性がありますが、このリストはロードマップとして機能します。FizzBuzzのテストリストは次のようになります。
- 負
- ゼロ
- 1
- 二
- 三
- 四
- 五
- 6(3の自明でない倍数)
- 9(3乗)
- 10(5の自明でない倍数)
- 15(3と5の倍数)
- 30(3&5の自明でない倍数)