ロジックを正しく実装した後もテストが失敗する場合(テストに誤りがあるため)、TDDでの最善のアクションは何ですか?
たとえば、次の関数を開発するとします。
int add(int a, int b) {
return a + b;
}
次の手順で開発するとします。
テストを書く(まだ機能なし):
// test1
Assert.assertEquals(5, add(2, 3));
コンパイルエラーが発生します。
ダミー関数の実装を記述します。
int add(int a, int b) {
return 5;
}
結果:test1
合格。
別のテストケースを追加します。
// test2 -- notice the wrong expected value (should be 11)!
Assert.assertEquals(12, add(5, 6));
結果:test2
失敗、test1
はまだ合格です。
実際の実装を書く:
int add(int a, int b) {
return a + b;
}
結果:test1
はまだ合格、test2
はまだ失敗します(11 != 12
)。
この特定のケースでは:次の方が良いでしょう:
test2
、それが通過することを確認します、またはtest2
で失敗し、正しい実装を再導入します(上記のステップ#4)。それとも、他の賢い方法がありますか?
問題の例は些細なものだと理解していますが、2つの数値を加算するよりも複雑になる可能性がある一般的なケースで何をするか興味があります。
[〜#〜] edit [〜#〜](@Thomas Junkの回答に応じて):
この質問の焦点は、そのような場合にTDDが示唆することであり、優れたコードまたはテスト(TDD方式とは異なる場合がある)を実現するための「普遍的なベストプラクティス」ではありません。
絶対に重要なことは、テストが成功したことと失敗したことの両方を目にすることです。
コードを削除してテストを失敗させるかどうかは、コードを書き換えるか、クリップボードにこっそりと貼り付けて後で貼り付けるかは関係ありません。 TDDは、何かを再入力する必要があるとは決して言っていません。合格する必要がある場合にのみテストに合格し、不合格になる場合にのみ失敗することを知りたいと考えています。
テストの成功と失敗の両方を見ることが、テストをテストする方法です。あなたが見たことがないテストを信頼してはいけません。
赤いバーに対するリファクタリング は、動作するテストをリファクタリングするための正式な手順を提供します。
ただし、実用的なテストのリファクタリングは行っていません。バギーテストを変換する必要があります。懸念事項の1つは、このテストだけでカバーされたにもかかわらず導入されたコードです。テストが修正されたら、そのようなコードをロールバックして再導入する必要があります。
それが当てはまらず、コードをカバーする他のテストのためにコードカバレッジが問題にならない場合は、テストを変換して、グリーンテストとして導入できます。
ここでは、コードもロールバックされていますが、テストが失敗するのに十分です。導入されたすべてのコードをカバーするのに十分ではなく、バグのあるテストのみでカバーされている場合は、より大きなコードのロールバックとより多くのテストが必要です。
グリーンテストを導入する
コードを壊すことは、コードをコメントアウトするか、後で貼り付けるためだけに他の場所に移動することです。これは、テストがカバーするコードの範囲を示しています。
これらの最後の2つの実行では、通常の赤と緑のサイクルに戻ります。入力せずに貼り付けるだけで、コードを解読してテストに合格します。したがって、テストに合格するのに十分なだけ貼り付けていることを確認してください。
ここでの全体的なパターンは、テストの色が期待どおりに変化することを確認することです。これにより、信頼できないグリーンテストが短時間発生する状況が発生することに注意してください。中断されたり、これらの手順のどこにいるのかを忘れないように注意してください。
赤いバーを埋め込む リンクについて RubberDuck に感謝します。
達成したい全体的な目標は何ですか?
素敵なテストをしていますか?
正しい実装をしていますか?
TTDを真剣に行っていますか?
上記のどれでもない?
おそらく、テストとテストとの関係を考えすぎているでしょう。
テストは、実装の正しさを保証しません。すべてのテストに合格しても、ソフトウェアが必要な処理を行うかどうかについては何も言えません。ソフトウェアに関するessentialisticステートメントはありません。
あなたの例を取る:
追加の「正しい」実装は、a+b
と同等のコードになります。そして、あなたのコードがそれを行う限り、あなたはアルゴリズムが正しいそれが何をしていて正しく実装されているか.
int add(int a, int b) {
return a + b;
}
最初の光景では、これがであるということに同意します追加の実装。
しかし私たちが実際にやっていることは言っていません。このコードはaddition
itの実装ですある程度までしか動作しないのように:整数のオーバーフローを考えてください。
整数オーバーフローはコードで発生しますが、addition
の概念では発生しません。つまり、コードはaddition
の概念のようにある程度動作しますが、addition
ではありません。
このかなり哲学的な見方は、いくつかの結果をもたらします。
そして1つは、テストとは、コードの予想される動作の仮定にすぎません。コードのテストでは、(おそらく)確認することはできません。実装はright、あなたが言うことができる最高のことは、あなたのコードが提供する結果に対するあなたの期待が満たされたか、満たされなかったことです。それがあなたのコードが間違っている、それがあなたのテストが間違っている、それともその両方が間違っている、など。
有用なテストは、コードが何をすべきかについての期待をfixするのに役立ちます:私が期待を変えない限り、および変更されたコードが与える限り私が期待している結果を私は確信しています。結果について私が行った仮定がうまく機能しているように見えることは確かです。
間違った仮定をした場合、それは役に立ちません。でもねえ!少なくともそれは統合失調症を防ぐ:何もないはずであるときに異なる結果を期待する。
tl; dr
ロジックを正しく実装した後もテストが失敗する場合(テストに誤りがあるため)、TDDでの最善のアクションは何ですか?
テストは、コードの動作に関する仮定です。実装が正しいと考える正当な理由がある場合は、テストを修正して、その仮定が成り立つかどうかを確認してください。
実装が間違っているとテストが失敗することを知っておく必要があります。これは、実装が正しい場合に合格することとは異なります。したがって、コードが失敗すると予想される状態にコードを戻す必要がありますbeforeテストを修正し、予期した理由(つまり、5 != 12
)、あなたが予測しなかった何かよりもむしろ。
この特定のケースでは、12を11に変更し、テストに合格した場合、テストと実装のテストは順調に完了していると思います。そのため、追加の作業を行う必要はほとんどありません。
ただし、セットアップコードに誤りがある場合など、より複雑な状況でも同じ問題が発生する可能性があります。その場合、テストを修正した後、その特定のテストが失敗するような方法で実装を変更してから、変更を元に戻す必要があります。実装を元に戻すことが最も簡単な方法である場合は、それで問題ありません。あなたの例では、a + b
からa + a
またはa * b
。
または、アサーションをわずかに変更してテストが失敗することを確認できる場合、テストのテストに非常に効果的です。
これは、お気に入りのバージョン管理システムの場合です。
テストの修正をステージングし、コードの変更を作業ディレクトリに保持します。
対応するメッセージFixed test ... to expect correct output
でコミットします。
git
では、テストと実装が同じファイルにある場合はgit add -p
の使用が必要になる場合があります。そうでない場合は、2つのファイルを別々にステージングするだけで十分です。
実装コードをコミットします。
時間に戻って、ステップ1で行ったコミットをテストします。テストが実際に失敗することを確認してください。
ご覧のとおり、失敗したテストをテストしている間は、編集能力に依存せずに実装コードを邪魔にならないように移動できます。 VCSを使用して作業を保存し、VCSに記録された履歴に失敗したテストと合格したテストの両方が正しく含まれるようにします。