TDDで、本番コードを変更せずに合格するテストケースを作成した場合、それはどういう意味ですか?


17

これらは、TDDに関するRobert C. Martinの規則です

  • 失敗した単体テストに合格しない限り、実稼働コードを作成することはできません。
  • 失敗するのに十分な数以上の単体テストを作成することはできません。コンパイルの失敗は失敗です。
  • 1つの単体テストに合格するのに十分な量を超える量産コードを記述することはできません。

価値があると思われるが、本番コードを変更せずに合格するテストを作成する場合:

  1. それは何か間違ったことをしたということですか?
  2. 役立つ場合は、将来そのようなテストを書くことを避けるべきですか?
  3. そのテストをそこに残すか、削除する必要がありますか?

注: 私はここでこの質問をしようとしていました:ユニットテストに合格することから始められますか? しかし、私は今まで十分に質問を明確にすることができませんでした。


あなたが引用した記事にリンクされている「Bowling Game Kata」には、実際に最終ステップとして即時合格テストがあります。
jscs

回答:


21

失敗する単体テストに合格するまで生産コードを書くことはできず、get-goから合格するテストを書くことはできないと書かれています。ルールの意図は、「実動コードを編集する必要がある場合は、最初にテストを作成または変更するようにしてください」と言うことです。

理論を証明するテストを作成することもあります。テストに合格し、それが私たちの理論に反しています。その後、テストを削除しません。ただし、(ソース管理の裏付けがあることを知って)プロダクションコードを壊して、予期しないときにパスした理由を確実に理解することができます。

有効で正しいテストであることが判明し、既存のテストを複製していない場合は、そのままにしておきます。


既存のコードのテストカバレッジを改善することは、(うまくいけば)合格するテストを作成するもう1つの完全に有効な理由です。
ジャック

13

次のいずれかを意味します。

  1. 最初にテストを作成せずに(「宗教的なTDD」の違反)、目的の機能を満たす生産コードを作成した、または
  2. 必要な機能はたまたま生産コードによってすでに実現されており、その機能をカバーする別の単体テストを書いているだけです。

後者の状況は、思っているよりも一般的です。完全に正真正銘の些細な(しかしまだ説明的な)例として、次の単体テスト(私は怠け者であるため、擬似コード)を書いたとしましょう。

public void TestAddMethod()
{
    Assert.IsTrue(Add(2,3) == 5);
}

本当に必要なのは、2と3を加算した結果だけだからです。

実装方法は次のとおりです。

public int add(int x, int y)
{
    return x + y;
}

しかし、4と6を一緒に追加する必要があるとしましょう。

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

メソッドはすでに2番目のケースをカバーしているので、メソッドを書き換える必要はありません。

ここで、Add関数が実際にある程度の上限がある数値(100など)を返す必要があることがわかったとしましょう。これをテストする新しいメソッドを作成できます。

public void TestAddMethod3()
{
    Assert.IsTrue(Add(100,100) == 100);
}

そして、このテストは失敗します。関数を書き直さなければなりません

public int add(int x, int y)
{
    var a = x + y;
    return a > 100 ? 100 : a;
}

それを通過させる。

常識は、

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

合格した場合、テストに合格するための新しいコードを作成できるように、テストが失敗するようにメソッドを意図的に失敗させることはありません。


5
マーティンの例を完全に守った(そして、彼があなたがそうすることを必ずしも示唆していない)場合、add(2,3)パスするために、文字通り5を返します。ハードコードされています。次に、テストを作成します。このテストでは、同時にadd(4,6)中断せずに合格する実動コードを作成する必要がありadd(2,3)ます。あなたは終わるだろうreturn x + y、あなたはそれを起動しないでしょう。理論的には。当然、マーティン(または多分他の誰かだったかもしれませんが、私は覚えていません)は教育のためにそのような例を提供することを好みますが、実際にそのような些細なコードをそのように書くことを期待していません。
アンソニーペグラム

1
@tieTYT、通常、マーティンの本から正しく思い出すと、2番目のテストケースは通常、単純なメソッドの一般的なソリューションを書くのに十分です(実際には、実際にそれを動作させるだけです)初めて)。サードの必要はありません。
アンソニーペグラム

2
@tieTYT、そうするまでテストを書き続けます。:)
アンソニーペグラム

4
3番目の可能性があり、それはあなたの例に反しています:重複したテストを書きました。したがって、TDDに「厳密に」従うと、合格する新しいテストは常に赤旗になります。DRYに続いて、本質的に同じことをテストする2つのテストを決して書かないでください。
コンガスボンガス

1
「Martinの例に完全に従ったなら(そして彼があなたがそうすることを必ずしも示唆していない)、add(2,3)を渡すには、文字通り5を返します。ハードコードされています。-これは、常に私に感謝してきた厳密なTDDのビットです。あなたが知っているコードを書くという考えは、将来のテストが来てそれを証明することを期待して間違っています。何らかの理由で、その将来のテストが決して書かれておらず、同僚が「all-tests-green」が「all-code-correct」を意味すると仮定した場合はどうなりますか?
ジュリアヘイワード

2

テストに合格しましたが、間違いではありません。生産コードは最初からTDDではないからだと思います。

canonical(?)TDDを想定します。本番コードはありませんが、いくつかのテストケースがあります(もちろん常に失敗します)。本番コードを追加して渡します。次に、ここで停止して、失敗したテストケースを追加します。再度、渡す実動コードを追加します。

つまり、テストは、単純なTDD単体テストではなく、一種の機能テストである可能性があります。それらは常に製品の品質にとって貴重な資産です。

私は個人的にそのような全体主義的で非人道的なルールが好きではありません;(


2

実際、昨夜道場で同じ問題が起こりました。

私はそれについて簡単な研究をしました。これは私が思いついたものです:

基本的に、TDDルールでは明示的に禁止されていません。関数が一般化された入力に対して正しく動作することを証明するために、いくつかの追加テストが必要な場合があります。この場合、TDDプラクティスはしばらくの間放置されます。その間生産コードが追加されていない限り、TDDプラクティスを間もなく終了しても必ずしもTDDルールに違反するわけではないことに注意してください。

冗長でない限り、追加のテストを作成できます。同等のクラスパーティションテストを行うことをお勧めします。つまり、エッジケースと、すべての同等クラスの少なくとも1つの内部ケースがテストされます。

ただし、このアプローチで発生する可能性のある問題の1つは、テストが最初から合格した場合、誤検出がないことを保証できないことです。つまり、テストが正しく実装されておらず、本番コードが正常に機能していないために合格するテストがある可能性があります。これを防ぐには、テストを中断するために製品コードをわずかに変更する必要があります。これによりテストが失敗した場合、テストは正しく実装されている可能性が高く、生産コードを元に戻してテストを再度パスできます。

厳密なTDDを練習したいだけなら、最初から合格する追加のテストを書かないかもしれません。一方、エンタープライズ開発環境では、追加のテストが有用と思われる場合、実際にはTDDプラクティスを終了する必要があります。


0

実動コードを変更せずに合格するテストは本質的に悪いものではなく、多くの場合、追加の要件または境界ケースを記述するために必要です。あなたのテストが「価値がある」とあなたが言うように、それを保持します。

問題が発生するのは、問題の領域を実際に理解するための代替として既に合格したテストを作成するときです。

2つの極端な例が考えられます。「念のため」多数のテストを作成する1人のプログラマーがバグをキャッチします。そして、最小限のテストを作成する前に問題空間を慎重に分析する2人目のプログラマー。両方が絶対値関数を実装しようとしているとしましょう。

最初のプログラマーの書き込み:

assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...

2番目のプログラマは次のように記述します。

assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1

最初のプログラマーの実装の結果は次のとおりです。

def abs(n):
    if n < 0:
        return -n
    elif n > 0:
        return n

2番目のプログラマーの実装の結果は次のとおりです。

def abs(n):
    if n < 0:
        return -n
    else:
        return n

すべてのテストに合格しますが、最初のプログラマーはいくつかの冗長テストを書いただけでなく(開発サイクルを不必要に遅くする)、境界ケースのテストに失敗しました(abs(0))。

実動コードを変更せずに合格するテストを作成している場合は、テストが本当に価値を高めているのか、問題空間を理解するためにより多くの時間を費やす必要があるのか​​を自問してください。


さて、2人目のプログラマーも、同僚の再定義abs(n) = n*nと合格により、明らかにテストに不注意でした。
栄子

@栄子あなたは絶対に正しい。書くことも少ない数のテストをと、同じくらいひどく噛みついてしまいます。二人目のプログラマーは少なくともテストをしなかったことで過度にけちだったabs(-2)。すべてと同様に、節度が重要です。
thinkterry
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.