実装を書いた後、テストの間違いを修正する方法


21

ロジックを正しく実装した後でもテストが失敗する場合(テストに間違いがあるため)、TDDでの最善のアクションは何ですか?

たとえば、次の関数を開発するとします。

int add(int a, int b) {
    return a + b;
}

次の手順で開発するとします。

  1. テストの書き込み(まだ機能なし):

    // test1
    Assert.assertEquals(5, add(2, 3));
    

    コンパイルエラーが発生します。

  2. ダミー関数の実装を作成します。

    int add(int a, int b) {
        return 5;
    }
    

    結果:test1合格。

  3. 別のテストケースを追加します。

    // test2 -- notice the wrong expected value (should be 11)!
    Assert.assertEquals(12, add(5, 6));
    

    結果:test2失敗しますが、test1成功します。

  4. 実際の実装を書く:

    int add(int a, int b) {
        return a + b;
    }
    

    結果:test1まだ合格、test2まだ失敗(以降11 != 12)。

この特定のケースでは:

  1. 訂正しtest2、そしてそれが今渡していることがわかり、または
  2. 実装の新しい部分を削除し(つまり、上記の手順2に戻って)、修正test2して失敗させてから、正しい実装を再度導入します(上記の手順4)。

または、他の巧妙な方法がありますか?

例の問題はかなり些細なものであることは理解していますが、2つの数字を追加するよりも複雑になる可能性のある一般的なケースで何をすべきか興味があります。

編集(@Thomas Junkの回答に応じて):

この質問の焦点は、そのような場合にTDDが示唆することであり、優れたコードまたはテスト(TDDの方法とは異なる場合があります)を達成するための「普遍的なベストプラクティス」ではありません。



5
明らかに、TDDでTDDを実行する必要があります。
Blrfl

17
TDDに懐疑的である理由を誰かが私に尋ねるなら、私は彼らにこの質問を指摘します。これはカフカスクです。
トラウベンフックス

D:あなたはTDDing«ながらTDDことができるようにXibitを教えてくれる何»私はTDDでTDDを入れています@Blrfl
トーマス・ジャンク

3
@Traubenfuchs私は質問が一見愚かに見えると認め、「私は常にTDDを行う」ことを主張していませんが、テストが失敗したのを見て、テストに合格するコードを書くことには大きな利点があると思います(これは結局のところ、この質問の実際の目的です)。
ビンセントサバード

回答:


31

絶対に重要なことは、テストが成功と失敗の両方を見るということです。

テストを失敗させるためにコードを削除してからコードを書き換えるか、後で貼り付けるためだけにクリップボードに忍び込ませるかは関係ありません。TDDは、再入力が必要だと言ったことはありません。テストに合格する必要がある場合にのみテストが合格し、失敗する場合にのみ失敗することを知りたい。

テストの合格と不合格の両方を確認することが、テストのテスト方法です。あなたが見たことのないテストを両方とも信頼しないでください。


レッドバーに対するリファクタリングは、作業テストをリファクタリングするための正式な手順を提供します。

  • テストを実行する
    • 緑のバーに注意してください
    • テスト中のコードを破る
  • テストを実行する
    • 赤いバーに注意してください
    • テストをリファクタリングする
  • テストを実行する
    • 赤いバーに注意してください
    • テスト中のコードを解読しない
  • テストを実行する
    • 緑のバーに注意してください

ただし、作業テストのリファクタリングは行っていません。バギーテストを変換する必要があります。懸念事項の1つは、このテストだけがカバーしている間に導入されたコードです。そのようなコードは、テストが修正されたらロールバックして再導入する必要があります。

それが当てはまらず、コードをカバーする他のテストが原因でコードカバレッジが問題にならない場合は、テストを変換してグリーンテストとして導入できます。

ここでは、コードもロールバックされていますが、テストが失敗するのに十分です。バグのあるテストでのみカバーされているのに、導入されたすべてのコードをカバーするには不十分な場合は、より大きなコードのロールバックとより多くのテストが必要です。

グリーンテストを導入する

  • テストを実行する
    • 緑のバーに注意してください
    • テスト中のコードを破る
  • テストを実行する
    • 赤いバーに注意してください
    • テスト中のコードを解読しない
  • テストを実行する
    • 緑のバーに注意してください

コードを壊すには、コードをコメントアウトするか、後で貼り付けるために別の場所に移動するだけです。これは、テストがカバーするコードの範囲を示しています。

これらの最後の2回の実行では、通常の赤緑サイクルに戻ります。コードを中断せずにテストをパスするために、入力する代わりに貼り付けるだけです。したがって、テストに合格するのに十分なだけ貼り付けてください。

ここでの全体的なパターンは、テストの色が予想どおりに変化することを確認することです。これにより、信頼できないグリーンテストが短時間行われるという状況が発生することに注意してください。これらの手順のどこにいるのかを忘れて忘れることに注意してください。

赤いバーのリンクを受け入れてくれたRubberDuckに感謝します。


2
私はこの答えが一番好きです:間違ったコードでテストが失敗するのを見ることが重要ですので、コードを削除/コメントアウトし、テストを修正して失敗し、コードを戻します(テストを置くために意図的なエラーを導入するかもしれませんテスト)を実行し、動作するようにコードを修正します。完全に削除して書き換えるのは非常にXPですが、時には実用的でなければなりません。;)
GolezTrol

@GolezTrol私の答えは同じことを言っていると思うので、それが不明確だったかどうかについてのフィードバックをいただければ幸いです。
-jonrsharpe

@jonrsharpeあなたの答えもいいですし、これを読む前にそれを支持しました。しかし、コードを元に戻すのが非常に厳しい場合、CandiedOrangeは、私にとってより魅力的な、より実用的なアプローチを提案します。
GolezTrol

@GolezTrol コードを元に戻す方法は言いませんでした。コメントアウト、切り取りと貼り付け、隠し、IDEの履歴の使用。本当に問題ではありません。重要なことはそれを行う理由です。そのため、正しい障害が発生していることを確認できます。私は編集したが、うまくいけば明確にしたい。
-jonrsharpe

10

達成したい全体的な目標は何ですか?

  • いいテストをする?

  • 作る正しい実装を?

  • TTDは宗教的に正しいですか?

  • 上記のどれでもない?

おそらく、あなたはテストとテストとの関係を考え直すでしょう。

テストでは、実装の正確性については保証されません。すべてのテストに合格しても、ソフトウェアが必要なことを行うかどうかについては何も言いません。それはあなたのソフトウェアについて本質的な声明を出しません。

あなたの例を挙げます:

追加の「正しい」実装は、と同等のコードになりa+bます。そして限り、あなたのコードのようにているが、あなたはアルゴリズムがあると言うでしょう正しいそれがないとされているもので、正しく実装されています。

int add(int a, int b) {
    return a + b;
}

一見、我々の両方が、このことを、同意するだろうであるほかの実装。

しかし、私たちが実際にやっていることは、このコードがあること、言っていないされているの実装addition、それはある程度しか振る舞いを考える:1のような整数オーバーフロー

整数オーバーフローコードで発生しますが、の概念では発生ませんaddition。そのため、コードはの概念のようにある程度動作しますadditionが、そうではありませんaddition

このかなり哲学的な観点には、いくつかの結果があります。

1つは、テストは、コードの予想される動作の仮定に過ぎないということです。コードのテストでは、(おそらく)実装正しいかどうかを確認することはできませんでした。言うことができるのは、コードが提供する結果に対する期待が満たされたかどうかです。コードが間違っているか、テストが間違っているか、コードが間違っているか、両方が間違っているかです。

有用なテストは、コードが何をすべきかについての期待を修正するのに役立ちます:期待を変更しない限り、修正されたコードが期待する結果を提供する限り、私は確信していると確信できます結果はうまくいくようです。

あなたが間違った仮定をしたとき、それは助けにはなりません。でもね!少なくとも統合失調症を予防します:あるはずがないときに異なる結果を期待します。


tl; dr

ロジックを正しく実装した後でもテストが失敗する場合(テストに間違いがあるため)、TDDでの最善のアクションは何ですか?

テストは、コードの動作に関する仮定です。実装が正しいと考える正当な理由がある場合は、テストを修正し、その仮定が当てはまるかどうかを確認します。


1
全体的な目標についての質問は非常に重要だと思います。私にとって、最高の優先事項は次のとおりです。1.正しい実装2.「適切な」テスト(または、「有用」/「適切に設計された」テスト)TDDは、これら2つの目標を達成するための可能なツールであると考えています。したがって、TDDを必ずしも宗教的にフォローする必要はありませんが、この質問の文脈では、TDDの観点に主に興味があります。これを明確にするために質問を編集します。
アッティリオ

それでは、オーバーフローをテストし、それが発生したときに合格するテストを作成しますか、それともアルゴリズムが追加であり、オーバーフローが間違った答えを生成するために発生したときに失敗させますか?
ジェリージェレミア

1
@JerryJeremiah私のポイントは、テストでカバーすべきことはユースケースに依存するということです。1桁の数字を追加するユースケースの場合、アルゴリズムは十分です。「大きな数字」を加算する可能性が非常に高いことを知っている場合、これdatatypeは明らかに間違った選択です。テストにより、次のことが明らかになります。期待は「大きな数の作品」となり、いくつかのケースでは満たされない。その場合、問題はそれらのケースに対処する方法になります。それらはコーナーケースですか?はいの場合、それらに対処する方法は?おそらく、いくつかのquard節は、より大きな混乱を防ぐのに役立ちます。答えはコンテキストに依存しています。
トーマスジャンク

7

実装が間違っている場合、テストが失敗することを知っておく必要があります。これは、実装が正しい場合に合格することとは異なります。したがって、テストを修正するに、コードを失敗する可能性のある状態に戻して、予期しない理由(つまり5 != 12)で失敗したことを確認する必要があります。


予想される理由でテストが失敗することをどのように確認できますか?
バシレフス

2
@Basilevs:1.失敗の理由がどうあるべきかについて仮説を立てます。2.テストを実行します。3.結果の失敗メッセージを読んで比較します。これにより、テストを書き直してより意味のあるエラーを表示する方法も提案される場合があります(たとえば、両方が同じことをテストしているassertTrue(5 == add(2, 3))場合よりassertEqual(5, add(2, 3))も有用でない出力を提供する)。
jonrsharpe

ここでこの原則をどのように適用するかはまだ不明です。私は仮説を持っています-テストは定数値を返します、同じテストを再度実行すると、私が正しいことを保証しますか?明らかにそれをテストするには、別のテストが必要です。回答に明確な例を追加することをお勧めします。
バシレフ

1
@Basilevs何?ここでのステ​​ップ3の仮説は、「5が12に等しくないため、テストは失敗します」です。テストを実行すると、その理由でテストが失敗するかどうかが示されます。その場合、続行するか、その他の理由でテストが失敗する場合は、理由がわかります。おそらくこれは言語の問題かもしれませんが、あなたが何を提案しているのかはわかりません。
-jonrsharpe

5

この特定のケースでは、12を11に変更し、テストに合格した場合、テストと実装のテストがうまくできたと思うので、追加のフープを行う必要はあまりありません。

ただし、セットアップコードに誤りがある場合など、より複雑な状況でも同じ問題が発生する可能性があります。その場合、テストを修正した後、おそらく特定のテストが失敗するように実装を変更してから、変更を元に戻す必要があります。実装を元に戻すことが最も簡単な方法である場合は、それで問題ありません。あなたの例では、またはに変異a + bするかもしれません。a + aa * b

あるいは、アサーションをわずかに変更してテストが失敗するのを見ることができれば、テストのテストに非常に効果的です。


0

これは、お気に入りのバージョン管理システムの場合です。

  1. テストの修正を段階的に行い、コードを変更して作業ディレクトリに保存します。
    対応するメッセージでコミットしますFixed test ... to expect correct output

    git使用するとgit add -p、テストと実装が同じファイルにある場合に使用する必要があります。そうでない場合は、明らかに2つのファイルを別々にステージングできます。

  2. 実装コードをコミットします。

  3. 時間をさかのぼって、手順1で行ったコミットをテストし、テストが実際に失敗することを確認します

失敗したテストのテスト中に、実装のコードを邪魔にならないようにするために、編集の腕前に依存することはありません。VCSを使用して作業を保存し、VCSに記録された履歴に失敗したテストと合格したテストの両方が正しく含まれていることを確認します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.