web-dev-qa-db-ja.com

TDDとリファクタリングの難しさ(または-なぜこれは本来あるべき以上に苦痛なのか?)

私は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プロセスで設計するよりも、事前に設計をスケッチしておけば、もっと早く理解できたと思わずにはいられません。

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

皆さんありがとう!

20
Landon

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

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

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

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

19
jhewlett

...私は仕方がありませんが、TDDプロセス中に設計させるのではなく、事前に設計をスケッチしておけば、これをすぐに見つけられたと感じます...

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

一方では、TDDは問題なく機能し、機能を構築するときに自動テストを提供し、インターフェイスを変更する必要がある場合はすぐに中断しました。

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

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

ただし、次回のレッスンを探している場合は、最初から始めてください。そして、最初に好きなだけデザインを考えます。

10
Steven A. Lowe

https://frontendmasters.com/courses/angularjs-and-code-testability/ 約2:22:00から終了(約1時間)まで。動画は無料ではありませんが、それをうまく説明している無料の動画は見つかりませんでした。

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

マジックは、コードテストを作成するのではなく、テスト可能なコードを作成することです。ユーザーのふりをするコードを書くことではありません。

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

0
boatcoder