web-dev-qa-db-ja.com

単体テスト/ TDDに役立つ設計パターンは?

リーディング この質問 は、ユニットテスト、TDDなどで常に抱えていた問題のいくつかを解決するのに役立ちました。

開発に対するTDDのアプローチに出会って以来、私はそれが従うべき正しい道であることを知っていました。さまざまなチュートリアルを読むことで、開始方法を理解するのに役立ちましたが、それらは常に非常に単純化されており、アクティブなプロジェクトに適用できるものではありません。私が管理した最良の方法は、コードの小さな部分(ライブラリなど、メインアプリで使用されますが、まったく統合されていないもの)に関するテストを作成することです。これは有用でしたが、コードベースの約5%に相当します。次のステップに進む方法については、メインアプリにいくつかのテストを追加するのに役立つことはほとんどありません。

ユニットテストのないほとんどのコードは、ハードな依存関係(つまり、すべてが新しい)や静的メソッドで構築されています。 "および" ...クラス間で高レベルのカップリング、クラス内のオブジェクトを構成するのが難しい[...]および "のおかげで、次のステップは、コードを分離してテスト可能にする方法を理解することだと気づきました。

これを行うには何を見ればいいですか?テストを容易にするために理解し、実装を開始する必要がある特定の設計パターンのセットはありますか?

50

ここでMike Cliftonは、2004年からの24のテストパターンについて説明します。単体テストを設計するときに役立つヒューリスティックです。

http://www.codeproject.com/Articles/5772/Advanced-Unit-Test-Part-V-Unit-Test-Patterns

合格/不合格パターン

これらのパターンは、優れたコードを保証するための最初の防御線(または見方によっては攻撃)です。しかし、注意してください、彼らは彼らがコードについてあなたに言うことを欺いています。

  • 単純なテストパターン
  • コードパスパターン
  • パラメータ範囲パターン

データトランザクションパターン

データトランザクションパターンは、データの永続性と通信の問題を受け入れることから始まります。このトピックの詳細については、「シミュレーションパターン」で説明します。また、これらのパターンでは、サーバーへのロードなど、ストレステストを意図的に省略しています。これについては、「ストレステストパターン」で説明します。

  • 単純なデータI/Oパターン
  • 制約データパターン
  • ロールバックパターン

コレクション管理パターン

アプリケーションが行うことの多くは、情報のコレクションを管理することです。プログラマーが利用できるコレクションにはさまざまなものがありますが、コードが正しいコレクションを使用していることを確認(および文書化)することが重要です。これは、順序と制約に影響します。

  • コレクション順序パターン
  • 列挙パターン
  • コレクション制約パターン
  • コレクションインデックスパターン

パフォーマンスパターン

単体テストは、機能だけでなくフォームにも関係する必要があります。テスト中のコードはどのくらい効率的に機能しますか?どのくらい速いのか?どのくらいのメモリを使用しますか?データ挿入とデータ検索を効果的にトレードオフしていますか?リソースを正しく解放しますか?これらはすべて、単体テストの対象となっているものです。単体テストにパフォーマンスパターンを含めることにより、実装者は到達する目標を達成し、その結果、より優れたコード、より優れたアプリケーション、そしてより幸せな顧客が生まれます。

  • パフォーマンステストパターン

プロセスパターン

ユニットテストは、ユニット、アプリケーションの基本的な機能をテストすることを目的としています。テストプロセスは受け入れテスト手順に委ねられるべきだと主張することができますが、私はこの議論に賛成しません。プロセスは、異なるタイプのユニットです。ユニットテスターを使用したテストプロセスは、他のユニットテストと同じ利点を提供します。プロセスが意図された方法を文書化し、ユニットテスターは、プロセスを順番どおりにテストすることによって実装者を支援し、潜在的なユーザーインターフェイスの問題を迅速に識別します。上手。 「プロセス」という用語には、状態の遷移とビジネスルールも含まれます。どちらも検証する必要があります。

  • プロセスシーケンスパターン
  • プロセス状態パターン
  • プロセスルールパターン

シミュレーションパターン

データトランザクションは、事前設定された構成、開いた接続、オンラインデバイス(例を挙げると)が必要になることが多いため、テストが困難です。モックオブジェクトは、データベース、Webサービス、ユーザーイベント、接続、および/またはコードが処理するハードウェアをシミュレートすることで救出されます。モックオブジェクトには、現実の世界では再現が非常に困難な障害状態を作成する機能もあります(損失の多い接続、遅いサーバー、ネットワークハブの障害など)。

  • モックオブジェクトパターン
  • サービスシミュレーションパターン
  • ビットエラーシミュレーションパターン
  • コンポーネントシミュレーションパターン

マルチスレッドパターン

マルチスレッドアプリケーションのユニットテストは、本質的に非同期であり、したがって非決定的であることを意図した条件を設定する必要があるため、おそらく最も難しい作業の1つです。このトピックはおそらくそれ自体が主要な記事なので、ここでは非常に一般的なパターンのみを提供します。さらに、多くのスレッディングテストを正しく実行するには、ユニットテスターアプリケーション自体が個別のスレッドとしてテストを実行し、1つのスレッドが待機状態になったときにユニットテスターが無効にならないようにする必要があります。

  • 信号パターン
  • デッドロック解決パターン

ストレステストパターン

ほとんどのアプリケーションは理想的な環境でテストされます。プログラマーは小さなデータセットを使用し、ネットワークトラフィックの少ない高速マシンを使用しています。現実の世界は大きく異なります。何かが完全に壊れる前に、アプリケーションはパフォーマンスが低下し、ユーザーへの応答が不十分またはエラーになる可能性があります。ストレス下でのコードのパフォーマンスを検証する単体テストは、理想的な環境での単体テストと同等以上の熱意で満たす必要があります。

  • バルクデータストレステストパターン
  • リソースストレステストパターン
  • 負荷テストパターン

プレゼンテーションレイヤーパターン

ユニットテストの最も困難な側面の1つは、情報がプレゼンテーションレイヤー自体でユーザーに正しく届いていること、およびアプリケーションの内部動作がプレゼンテーションレイヤーの状態を正しく設定していることを確認することです。多くの場合、プレゼンテーションレイヤーは、ビジネスオブジェクト、データオブジェクト、および制御ロジックと絡み合っています。プレゼンテーションレイヤーのユニットテストを計画している場合は、問題の明確な分離が必須であることを理解する必要があります。ソリューションの一部には、適切なModel-View-Controller(MVC)アーキテクチャの開発が含まれます。 MVCアーキテクチャは、プレゼンテーションレイヤーを操作するときに優れた設計手法を開発する手段を提供します。ただし、簡単に悪用されます。 Wordだけでなく、実際にMVCアーキテクチャを正しく実装していることを確認するには、ある程度の規則が必要です。

  • ビューステートテストパターン
  • モデル状態のテストパターン
54
Dave

私はあなたがテストするために主に2つのものが必要であると私は言うでしょう、そしてそれらは協力して行きます:

  • インターフェース、インターフェース、インターフェース
  • 依存性注入;これをインターフェイスと組み合わせて使用​​すると、テストしたいモジュールを分離するためにパーツを自由に交換できます。他のサービスに通知を送信するcronのようなシステムをテストしたいですか?それをインスタンス化し、正しいインターフェイスに従うコンポーネントによって実際のコード実装を他のものに置き換えますが、テストしたい方法で反応するようにハードワイヤードされています:メール通知?例外をスローして、SMTPサーバーがダウンしたときに何が起こるかをテストする

私自身は単体テストの技術を習得していません(そして、私はそれにはほど遠いです)が、これが私の主な努力が現在進んでいるところです。問題は、私がまだテスト用に設計していないことであり、その結果、私のコードは対応するために後方に曲がらなければなりません...

10
samy

マイケルフェザーの本 レガシーコードを効果的に使用する はまさにあなたが探しているものです。彼はレガシーコードを「テストなしのコード」と定義し、テストする方法について話します。

ほとんどの場合と同様に、一度に1つのステップです。変更または修正を行う場合は、テストカバレッジを増やしてください。時間が経つにつれ、より完全なテストセットが作成されます。結合を減らすための手法と、アプリケーションロジック間でテストピースを適合させる方法について説明します。

他の回答で述べたように、依存性注入は、テスト可能な(そして一般に疎結合された)コードを書くための1つの良い方法です。

7
Paul Rubel

アレンジ、アクト、アサートは、特定のユースケースを中心にテストコードを構造化するのに役立つパターンの良い例です。

これは、パターンを示す架空のC#コードです。

[TestFixture]
public class TestSomeUseCases() {

    // Service we want to test
    private TestableServiceImplementation service;

    // IoC-injected mock of service that's needed for TestableServiceImplementation
    private Mock<ISomeService> dependencyMock;

    public void Arrange() {
        // Create a mock of auxiliary service
        dependencyMock = new Mock<ISomeService>();
        dependencyMock.Setup(s => s.GetFirstNumber(It.IsAny<int>)).Return(1);

        // Create a tested service and inject the mock instance
        service = new TestableServiceImplementation(dependencyMock.Object);
    }

    public void Act() {
        service.ProcessFirstNumber();
    }

    [SetUp]
    public void Setup() {
        Arrange();
        Act();
    }

    [Test]
    public void Assert_That_First_Number_Was_Processed() {
        dependencyMock.Verify(d => d.GetFirstNumber(It.IsAny<int>()), Times.Exactly(1));
    }
}

テストするシナリオがたくさんある場合は、具体的なアレンジ&アクトビット(または単にアレンジ)で共通の抽象クラスを抽出し、テスト関数をグループ化する継承クラスに残りの抽象ビット&テスト関数を実装できます。

4
Art

Gerard MeszarosのxUnitテストパターン:リファクタリングテストコードは、ユニットテスト用のパターンでいっぱいです。 TDDのパターンを探しているのは知っていますが、この本にはたくさんの役立つ資料があると思います。

この本はサファリについて書かれているので、中身をよく見て、参考になるかどうかを確認できます。 http://my.safaribooksonline.com/978013149505

4
Mark Irvine

次のステップは、コードを分離してテスト可能にする方法を理解することだと気づかされました。

これを行うために私は何を見なければなりませんか?テストを容易にするために理解し、実装を開始する必要がある特定の設計パターンのセットはありますか?

右に! [〜#〜] solid [〜#〜] はあなたが探しているものです(そうです、本当に)。私はこれらをお勧めし続けます 2電子ブック 、特に当面の問題のためにSOLID)の1つ。

また、既存のコードベースに単体テストを導入する場合は、非常に難しいことも理解する必要があります。残念ながら、密結合コードはあまりにも一般的です。これは、そうしないという意味ではありませんが、しばらくの間は、あなたが述べたのと同じように、テストはより小さな部分に集中します。

時間の経過とともにこれらはより大きな範囲に拡大しますが、それは既存のコードベースのサイズ、チームのサイズ、および問題に追加する代わりに実際に何人がそれを実行しているかに依存します。

3
eglasius

デザインパターンは実装の詳細であるため、TDDには直接関係ありません。パターンが存在するという理由だけでパターンをコードに適合させようとするのではなく、コードが進化するにつれてパターンが現れる傾向があります。また、コードが臭い場合にも役立ちます。これらの問題の解決に役立ちます。デザインパターンを念頭に置いてコードを開発せず、コードを記述してください。次に、テストに合格し、リファクタリングします。

2
Grant Palin

このような多くの問題は、適切なカプセル化で解決できます。または、懸念事項を混在させている場合は、この問題が発生する可能性があります。ユーザーを検証し、ドメインオブジェクトを検証し、ドメインオブジェクトをすべて1つのメソッドまたはクラスに保存するコードがあるとします。あなたはあなたの懸念を混合しました、そしてあなたは幸せになるつもりはありません。それらを個別にテストできるように、それらの懸念事項(認証/承認、ビジネスロジック、永続性)を分離する必要があります。

デザインパターンは役立ちますが、エキゾチックなパターンの多くは、適用できる非常に狭い問題を抱えています。コンポジット、コマンドなどのパターンは頻繁に使用され、シンプルです。

ガイドラインは次のとおりです。何かをテストすることが非常に難しい場合は、おそらくそれを小さな問題にリファクタリングし、リファクタリングされたビットを個別にテストできます。したがって、5行のifステートメントといくつかのforループを含む200行のメソッドがある場合、その不便さを解消する必要があるかもしれません。

したがって、まず、懸念事項を分離することで複雑なコードを単純化できるかどうかを確認し、次に、分割することで複雑なコードを単純化できるかどうかを確認します。もちろん、デザインパターンが飛びついたら、それを試してください。

1
hvgotcodes

依存性注入/ IoC。 SpringFrameworkやgoogle-guiceなどの依存関係注入フレームワークについても読んでください。また、テスト可能なコードの記述方法も対象としています。

0
walters