8

私が理解していることから、TDD では、最初に失敗するテストを作成し、次に合格するコードを作成し、次にリファクタリングする必要があります。しかし、コードがテストしたい状況をすでに説明している場合はどうでしょうか?

たとえば、ソート アルゴリズムを TDD しているとします (これは単なる仮説です)。いくつかのケースの単体テストを書くかもしれません:

入力 = 1、2、3
出力 = 1、2、3

入力 = 4、1、3、2
出力 = 1、2、3、4
など...

テストに合格するために、私は手っ取り早いバブル ソートを使用します。次に、リファクタリングして、より効率的なマージソート アルゴリズムに置き換えます。後で、安定ソートにする必要があることに気がついたので、そのためのテストも書きます。もちろん、マージソートは安定したソート アルゴリズムであるため、テストが失敗することはありません。とにかく、誰かが別の不安定なソートアルゴリズムを使用するために再度リファクタリングする場合に備えて、このテストが必要です。

これは、常に失敗するテストを作成するという TDD のマントラを破っていますか? テストケースをテストするためだけに不安定なソートアルゴリズムを実装する時間を無駄にしてから、マージソートを再実装することを勧める人はいないでしょう。同様の状況にどのくらいの頻度で遭遇し、何をしますか?

4

12 に答える 12

16

最初に失敗したテストを作成してから実行する理由は 2 つあります。

1つ目は、テストが実際にあなたが書いたものをテストしているかどうかを確認することです。最初に失敗するかどうかを確認し、コードを変更してテストを実行してから、実行するかどうかを確認します。ばかげているように思えますが、既に実行されているコードのテストを追加して、テストで間違いを犯したことが後で判明したことが何度かありました。

2 つ目の最も重要な理由は、テストを書きすぎないようにするためです。テストは設計を反映し、設計は要件と要件の変更を反映します。これが発生したときに、多くのテストを書き直す必要はありません。経験則としては、すべてのテストを 1 つの理由だけで失敗させ、その理由で失敗するテストを 1 つだけにすることです。TDD は、コードベースのすべてのテスト、すべての機能、およびすべての変更に対して、標準の赤-緑-リファクタリング サイクルを繰り返すことで、これを強制しようとします。

しかし、もちろん、ルールは破るために作られています。そもそもなぜこれらのルールが作られているのかを心に留めておけば、柔軟に対応できます。たとえば、複数のことをテストするテストがあることがわかった場合は、それを分割できます。事実上、これまでに失敗したことのない 2 つの新しいテストを作成しました。コードを壊してから修正して、新しいテストが失敗することを確認することは、物事を再確認する良い方法です。

于 2008-12-11T21:07:37.243 に答える
5

テストケースをテストするためだけに不安定なソートアルゴリズムを実装する時間を無駄にしてから、マージソートを再実装することを勧める人はいないでしょう。同様の状況にどのくらいの頻度で遭遇し、何をしますか?

それでは、私がそれをお勧めします。:)

これらすべては、一方で費やす時間と、他方で軽減または軽減するリスク、および得られる理解との間のトレードオフです。

仮定の例を続けます...

「安定性」が重要なプロパティ/機能であり、失敗させて「テストをテスト」しない場合、その作業を行う時間を節約できますが、テストが間違っていて常に緑色になるリスクがあります。

一方、機能を壊して失敗するのを見て「テストをテスト」する場合は、テストのリスクを減らします。

そして、ワイルドカードは、いくつかの重要な知識を得るかもしれないということです. たとえば、「悪い」並べ替えをコーディングしてテストを失敗させようとしているときに、並べ替えている型の比較制約についてより深く考えて、「x==y」を型として使用していたことに気付くかもしれません。ソートには equivalence-class-predicate を使用しますが、実際には "!(x<y) && !(y<x)" がシステムにとってより適切な述語です (たとえば、バグや設計上の欠陥を発見する可能性があります)。

だから私は、「失敗させるために余分な時間を費やしてください.いくらかの時間コストがかかりますが、ときどき巨大なバンドルを節約できます (たとえば、テストのバグは、システムの最も重要なプロパティをテストしていなかったことを意味します。または、不等式述語の設計全体が台無しになっています)上)。それは宝くじをするようなものですが、オッズは長期的にはあなたに有利です。毎週チケットに $5 を費やし、通常は負けますが、3 か月に 1 回、$1000 のジャックポットを獲得します。

于 2008-12-15T20:10:55.267 に答える
4

The one big advantage to making the test fail first is that it ensures that your test is really testing what you think. You can have subtle bugs in your test that cause it to not really test anything at all.

For example, I once saw in our C++ code base someone check in the test:

assertTrue(x = 1);

Clearly they didn't program so that the test failed first, since this doesn't test anything at all.

于 2008-12-11T21:00:01.097 に答える
3

単純な TDD ルール:失敗する可能性があるテストを記述します。

ソフトウェア エンジニアリングが教えてくれたことがあるとすれば、それは、テスト結果を予測できないということです。失敗すらない。実際、既存のソフトウェアですでに機能している「新機能のリクエスト」を目にすることはよくあります。多くの新機能は、既存のビジネス要件を単純に拡張したものであるため、これはよくあることです。基盤となる直交ソフトウェア設計は引き続き機能します。

つまり、「最大 5 項目」ではなく「リスト X は最大 10 項目を保持する必要がある」という新機能では、新しいテスト ケースが必要になります。List X の実際の実装で 2^32 の項目が許可されている場合、テストは成功しますが、新しいテストを実行するまで、それはわかりません。

于 2008-12-15T10:28:04.430 に答える
2

「テストファースト」開発だけでなく、TDD でテストを作成する理由があります。

並べ替えメソッドに、単純な並べ替えアクション以外のいくつかのプロパティがあるとします。たとえば、すべての入力が整数であることを検証します。最初はこれに依存せず、仕様にもないため、テストはありません。

後で、この追加の動作を悪用することにした場合は、テストを作成して、リファクタリングを行う他の誰かが、現在依存しているこの追加の動作を壊さないようにする必要があります。

于 2008-12-11T21:26:05.160 に答える
2

筋金入りの TDD 担当者は、肯定的なテストが誤検出ではないことを確認するために常に失敗したテストが必要だと言うでしょうが、実際には多くの開発者が失敗したテストをスキップしていると思います。

于 2008-12-11T21:00:49.953 に答える
2

新しいコードを書く場合は、テストを書き、次にコードを書きます。これは、最初は必ずテストが失敗することを意味します (ダミー インターフェイスに対して実行されるため)。その後、何度かリファクタリングを行うことができます。その場合、追加のテストを作成する必要はありません。既にあるテストで十分である可能性があるからです。

ただし、TDD メソッドを使用して一部のコードを維持したい場合があります。この場合、最初にテストを特性評価テスト (動作中のインターフェイスに対して実行されるため、定義上、失敗することはありません) として記述し、次にリファクタリングする必要があります。

于 2008-12-11T21:00:58.410 に答える
1

ええと...私はTDDサイクルを次のように読みました

  • コードが単なるスタブであるため、最初にテストを記述します。これは失敗します。
  • テストが通るようにコードを書く
  • 必要に応じてリファクタリングする

失敗するテストを書き続ける義務はありません。何もするコードがないため、最初のテストは失敗します。最初のテストのポイントは、インターフェイスを決定することです!

編集:「赤緑リファクタリング」マントラについて誤解があるようです。ウィキペディアのTDDの記事によると

テスト駆動開発では、各新機能はテストを作成することから始まります。このテストは、機能が実装される前に作成されているため、必然的に失敗します。

言い換えれば、不合格テストは新しい機能のためのものであり、追加のカバレッジのためのものではありません!

編集: もちろん、バグを再現するための回帰テストの作成について話している場合を除きます!

于 2008-12-11T21:00:23.980 に答える
1

あなたが提供した例は、最初の試行に合格するテストを作成する適切な時期の 1 つである IMO です。適切なテストの目的は、システムの予想される動作を文書化することです。予想される動作をさらに明確にするために、実装を変更せずにテストを作成してもかまいません。

PS

私が理解しているように、テストを成功させる前にテストを失敗させたい理由は次のとおりです。

「失敗することがわかっているテストを書くが、合格する前にテストする」という理由は、時々、テストが確実に失敗するという当初の仮定が間違っているからです。そのような場合、テストによって不要なコードを書く必要がなくなりました。

于 2009-01-11T18:29:00.503 に答える
1

しかし、コードがテストしたい状況をすでに説明している場合はどうでしょうか?

これは、常に失敗するテストを作成するという TDD のマントラを破っていますか?

はい、コードの前にテストを書くというマントラをすでに破っているためです。コードを削除して最初からやり直すか、テストが最初から機能することをそのまま受け入れることができます。

于 2008-12-11T21:36:33.360 に答える
1

他の人が言ったように、TDD のモットーは「失敗する単体テストなしに新しいコードはありません」です。TDDの実践者が「コードの欠落なしに新しいテストはない」と言っているのを聞いたことがありません。「たまたま」「たまたま」合格したとしても、新しいテストはいつでも大歓迎です。コードを壊れるように変更してから、テストに合格するために元に戻す必要はありません。

于 2009-01-26T05:59:36.853 に答える
0

私は何度もこの状況に遭遇しました。私はTDDをお勧めし、使用しようとしていますが、フローを壊しすぎてテストを停止して書くことができない場合があります。

私は2段階の解決策を持っています:

  1. 動作するコードと失敗しないテストができたら、意図的にコードに変更を挿入して、テストが失敗するようにします。
  2. 元のコードからその変更を切り取り、コード メソッドまたはテスト メソッドのいずれかのコメントに入れます。そうすれば、次回誰かがテストがまだ失敗を拾っていることを確認したいときに、何をすべきかがわかります。これは、テストが失敗をピックアップしたことを確認したという事実の証明にもなります。最もクリーンな場合は、コード メソッドに残します。条件付きコンパイルを使用してテスト ブレーカーを有効にするまで、これを利用することもできます。
于 2009-01-26T04:48:41.193 に答える