TDDテストで、テストも必要な新しい機能が必要になった場合はどうすればよいですか?


13

テストを作成していて、テストに合格する必要があり、独自の機能に分離する必要がある追加の機能が必要であることに気付いたとき、何をしますか?その新しい関数もテストする必要がありますが、TDDサイクルでは、テストを失敗させ、成功させてからリファクタリングするように指示されています。テストに合格しようとしているステップにいる場合、実装する必要がある新しい機能をテストするために、別の失敗したテストを開始することは想定されていません。

たとえば、関数WillCollideWith(LineSegmentを持つポイントクラスを作成しています。

public class Point {
    // Point data and constructor ...

    public bool CollidesWithLine(LineSegment lineSegment) {
        Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
                                               Position.Y + Velocity.Y);
        LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
        if (lineSegment.Intersects(pointPath)) return true;
        return false;
    }
}

LineSegment.Intersects(LineSegment関数が必要だと気づいたとき、CollidesWithLineのテストを書いていました。しかし、この新しい機能を作成するために、テストサイクルで実行していることを停止する必要がありますか?これは「赤、緑、リファクタリング」の原則を破っているようです。

lineSegments IntersectをCollidesWithLine関数内で検出し、動作後にリファクタリングするコードを記述する必要がありますか?LineSegmentからデータにアクセスできるので、この場合はうまくいきますが、その種のデータがプライベートな場合はどうでしょうか?

回答:


14

テストと最近のコードをコメントアウトするだけで(またはスタッシュに入れて)、実際にクロックをサイクルの開始に戻しました。次に、LineSegment.Intersects(LineSegment)テスト/コード/リファクタリングから始めます。それが完了したら、以前のテスト/コードのコメントを外し(またはstashからプル)、サイクルを続けます。


これはどのように違うのですか?それを無視して後で戻ってくるだけですか?
ジョシュアハリス

1
ほんの少しの詳細:レポートに余分な「無視」テストはありません。また、スタッシュを使用する場合、コードは「クリーン」なケースと区別できません。
ハビエル

スタッシュとは何ですか?バージョン管理のようなものですか?
ジョシュアハリス

1
一部のVCSはそれを機能として実装しています(少なくともGitとFossil)。変更を削除できますが、後で再適用するために保存できます。手動で行うのは難しくなく、差分を保存して最後の状態に戻すだけです。後で差分を再適用し、続けます。
ハビエル

6

TDDサイクル:

「テストに合格する」フェーズでは、テストに合格する最も簡単な実装を作成することになっています。テストに合格するために、不足しているロジックを処理する新しいコラボレーターを作成することにしました。これは、テストに合格するにはポイントクラスに入れるのが多すぎるためです。そこで問題が発生します。あなたが合格するために結んでいたテストはあまりに大きなステップだったと思います。したがって、問題はテスト自体にあると思うので、そのテストを削除/コメント化し、LineSegment.Intersects(LineSegment)パーツを導入せずに赤ちゃんの一歩を踏み出すことができる簡単なテストを見つけ出す必要があります。テストに合格したら、リファクタリングできますこの新しいロジックをLineSegment.Intersects(LineSegment)メソッドに移動して、コード(ここではSRPの原則を適用します)。動作を変更せずにコードを移動しただけなので、テストは引き続きパスします。

現在の設計ソリューションについて

しかし、私にとって、ここであなたがより深い設計上の問題を抱えているのは、あなたが単一責任原則に違反しているということです。ポイントの役割は....ポイントになること、それだけです。ポイントであることにスマートさはありません。それはただxとyの値です。ポイントは値のタイプです。これはセグメントでも同じです。セグメントは2つのポイントで構成される値のタイプです。たとえば、ポイントの位置に基づいて長さを計算するために、「スマート」を少し含めることができます。しかし、それだけです。

ここで、ポイントとセグメントが衝突しているかどうかを判断することは、それ自体が全体的な責任です。また、ポイントやセグメントが単独で処理するには作業が多すぎることは確かです。それ以外の場合、ポイントはセグメントを認識するため、Pointクラスに属することはできません。また、セグメントは、セグメント内のポイントを処理し、セグメント自体の長さを計算する責任があるため、セグメントに属することはできません。

したがって、この責任は、たとえば次のようなメソッドを持つ「PointSegmentCollisionDetector」などの別のクラスが所有する必要があります。

bool AreInCollision(ポイントp、セグメントs)

そして、それはポイントとセグメントから別々にテストするものです。

その設計の良いところは、衝突検出器の異なる実装ができるようになったことです。そのため、たとえば、実行時に衝突検出方法を切り替えることで、ゲームエンジンのベンチマーク(game:pを作成していると仮定します)を簡単に行うことができます。または、異なる衝突検出戦略間で実行時に視覚的なチェック/実験を行うため。

現時点では、このロジックをポイントクラスに配置することで、物事をロックダウンし、Pointクラスに多くの責任を負わせています。

それが理にかなっていることを願って、


:あなたは正しい、私がテストに変化が大きすぎるとしていたと私はあなたが右の衝突クラスにそのうちにseperatingうとしていると思うが、それは私はあなたが私を助けることができるかもしれないという全く新しい質問を作ること、万一Iメソッドが似ているだけのときにインターフェースを使用しますか?
ジョシュアハリス

2

TDD方式で行う最も簡単な方法は、LineSegmentのインターフェースを抽出し、メソッドパラメーターを変更してインターフェースを取得することです。次に、入力ラインセグメントをモックし、Intersectメソッドを個別にコーディング/テストできます。


1
私はそれが最もよく耳にするTDDメソッドであることを知っていますが、ILineSegmentは意味をなしません。外部リソースまたは多くの形式で提供される可能性のあるものとインターフェイスすることは1つのことですが、機能をラインセグメント以外にアタッチする理由がわかりません。
ジョシュアハリス

0

jUnit4を使用すると、@Ignore延期するテストの注釈を使用できます。

延期する各メソッドに注釈を追加し、必要な機能のテストの作成を続けます。古いテストケースを後でリファクタリングするには、丸で囲みます。

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