TDDとリファクタリングの難しさ(または-なぜこれが本来よりも痛いのか?)


20

TDDアプローチを使用することを自分で学びたいと思っていたので、しばらくの間、やりたいプロジェクトがありました。大規模なプロジェクトではなかったので、TDDの良い候補になると思いました。しかし、何かがおかしくなったように感じます。例を挙げましょう:

高レベルでは、私のプロジェクトはMicrosoft OneNoteのアドインであり、プロジェクトをより簡単に追跡および管理できます。また、いつか自分のカスタムストレージとバックエンドを構築することにした場合に備えて、このためのビジネスロジックをOneNoteから可能な限り切り離したままにしておきたいと考えました。

最初に、基本的な平易な言葉の受け入れテストから始めて、最初の機能で何をしたいかを概説しました。これは次のようなものです(簡潔にするために説明を省略しています)。

  1. ユーザーがプロジェクトの作成をクリックします
  2. プロジェクトのタイトルにユーザーが入力する
  3. プロジェクトが正しく作成されたことを確認します

UIのものといくつかの中間計画をスキップして、最初のユニットテストに行きます。

[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
    var testController = new Controller();
    Project newProject = testController(A.Dummy<String>());
    Assert.IsNotNull(newProject);
}

ここまでは順調ですね。赤、緑、リファクタリングなど。今では実際に保存する必要があります。ここでいくつかの手順を省略して、これで終わります。

[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
    var fakeDataStore = A.Fake<IDataStore>();
    var testController = new Controller(fakeDataStore);
    String expectedTitle = fixture.Create<String>("Title");
    Project newProject = testController(expectedTitle);

    Assert.AreEqual(expectedTitle, newProject.Title);
}

私はまだこの時点で気分が良いです。私はまだ具体的なデータストアを持っていませんが、私はそれがどのように見えるか予想したインターフェイスを作成しました。

この投稿は十分に長くなっているので、ここでいくつかの手順をスキップしますが、同様のプロセスに従って、最終的にデータストアのこのテストに進みます。

[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
    /* snip init code */
    testDataStore.SaveNewProject(A.Dummy<IProject>());
    A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}

これを実装するまでは良かったです。

public String SaveNewProject(IProject project)
{
    Page projectPage = oneNoteInterop.CreatePage(...);
}

そして、「...」のあるところに問題があります。この時点で、CreatePageにはセクションIDが必要であることがわかりました。コントローラーに関連するビットのテストのみに関心があったため、コントローラーレベルで考えていたときに、このことに気づきませんでした。ただし、ここまでずっと、プロジェクトを保存する場所をユーザーに尋ねる必要があることに気付きました。ここで、データストアに場所IDを追加し、プロジェクトに1つ追加し、次にコントローラーに1つ追加し、それらすべてに対して既に書き込まれているすべてのテストに追加する必要があります。すぐに退屈になり、TDDプロセス中に設計するのではなく、事前に設計をスケッチしておけば、これをもっと早く理解できたように思わずにはいられません。

このプロセスで何か間違ったことをした場合、誰かに説明してもらえますか?とにかく、この種のリファクタリングは回避できますか?それともこれは一般的ですか?それが一般的であれば、それをより痛みのないものにする方法はありますか?

皆さんありがとう!


このディスカッションフォーラム(groups.google.com/forum / #! forum / ...)でこのトピックを投稿すると、非常に洞察力に富んだコメントが得られます。これはTDDトピック専用です。
チャッククルツィンガー

1
すべてのテストに何かを追加する必要がある場合、テストの記述が不十分であるように思われます。テストをリファクタリングし、適切なフィクスチャの使用を検討する必要があります。
デイブヒリアー

回答:


19

TDDは(当然)ソフトウェアを設計および成長させる方法として宣伝されていますが、設計とアーキテクチャを事前に検討することをお勧めします。IMO、「事前に設計をスケッチする」は公正なゲームです。ただし、多くの場合、これはTDDを介して導かれる設計上の決定よりも高いレベルになります。

また、状況が変化した場合、通常はテストを更新する必要があることも事実です。これを完全に排除する方法はありませんが、テストの脆弱性を減らし、痛みを最小限に抑えるためにできることがいくつかあります。

  1. 可能な限り、実装の詳細をテストに含めないでください。これは、パブリックメソッドを介したテストのみを意味し、可能な場合は対話ベースの検証よりも状態ベースの検証を優先します。つまり、そこに到達するための手順ではなく、何かの結果をテストする場合、テストの脆弱性は低くなるはずです。

  2. 実動コードと同じように、テストコードの重複を最小限に抑えます。この投稿は参考になります。あなたの例ではID、いくつかの異なるテストでコンストラクターを直接呼び出したため、コンストラクターにプロパティを追加するのは苦痛だったようです。代わりに、オブジェクトの作成をメソッドに抽出するか、テスト初期化メソッドのテストごとにオブジェクトを初期化してみてください。


状態ベースと対話ベースのメリットを読み、ほとんどの場合それを理解しています。ただし、テストのためにプロパティを明示的に公開することなく、どのような場合にそれが可能かはわかりません。上記の例をご覧ください。「MustHaveBeenCalled」のアサーションを使用せずにデータストアが実際に呼び出されたことを確認する方法がわかりません。ポイント2に関しては、あなたは絶対に正しいです。私はすべての編集の後にそれをやり遂げましたが、私は自分のアプローチが一般に受け入れられているTDDプラクティスと一貫していることを確認したかっただけです。ありがとう!
ランドン

@Landon相互作用テストがより適切な場合があります。たとえば、データベースまたはWebサービスが呼び出されたことを確認します。基本的に、特に外部サービスからテストを分離する必要があるときはいつでも。
ジューレット

@Landon私は「説得力のある古典主義者」なので、相互作用ベースのテストにあまり慣れていません...しかし、「MustHaveBeenCalled」について断言する必要はありません。挿入をテストする場合は、クエリを使用して挿入が行われたかどうかを確認できます。PS:データベース層以外のすべてをテストするときは、パフォーマンスを考慮してスタブを使用します。
Hbas

@jhewlettこれも私が着いた結論です。ありがとう!
ランドン

@Hbasクエリするデータベースはありません。私はそれがあれば最も簡単な方法であることに同意しますが、これをOneNoteノートブックに追加しています。代わりにできることは、ページをプルしようとする相互運用ヘルパークラスにGetメソッドを追加することです。私はそれを行うためにテストを書くことができましたが、私は一度に2つのことをテストするように感じました:これを保存しましたか?ヘルパークラスはページを正しく取得しますか?ただし、ある時点で、テストは他の場所でテストされた他のコードに依存する必要があるかもしれません。ありがとう!
ランドン

10

...私は仕方がありませんが、TDDプロセス中にデザインを設計するのではなく、事前に設計をスケッチしておけば、これをもっと早く捕まえることができたように感じます...

多分そうでないかもしれません

一方では、TDDは正常に機能し、機能を構築するときに自動化されたテストを提供し、インターフェイスを変更する必要があるとすぐに中断しました。

一方、低レベルの機能(CreateProject)ではなく高レベルの機能(SaveProject)で開始した場合は、パラメーターがすぐに欠落していることに気づくでしょう。

もう一度、多分あなたは持っていないでしょう。これは再現不可能な実験です。

ただし、次回のレッスンを探している場合は、先頭から始めてください。そして、最初に必要なだけ設計について考えてください。


0

https://frontendmasters.com/courses/angularjs-and-code-testability/ 約2:22:00から終了まで(約1時間)。ビデオが無料ではないことを残念に思うが、私はそれをそれほどうまく説明する無料のビデオを見つけていない。

テスト可能なコードを記述するための最高のプレゼンテーションの1つは、このレッスンです。これはAngularJSクラスですが、主に彼が話していることは言語とは関係がなく、最初に良いテスト可能なコードを書くことに関係しているため、テスト部分はすべてJavaコードです。

魔法は、コードテストを書くのではなく、テスト可能なコードを書くことにあります。ユーザーを装ったコードを書くことではありません。

また、テストアサーションの形式で仕様を作成するのにも時間を費やしています。

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