モキとスタブ

テストが非常に簡単なクラスのカテゴリがあります。 クラスがプリミティブデータ型のみに依存し、他のビジネスエンティティとの接続を持たない場合は、このクラスのインスタンスを作成し、プロパティを変更するかメソッドを呼び出して期待される状態を確認することで何らかの方法で「キック」します。



これは最も簡単で効果的なテスト方法であり、賢明な設計はこのようなクラスに基づいています。これらのクラスは下位レベルの「ビルディングブロック」であり、それに基づいてより複雑な抽象化が構築されます。 しかし、そのような「孤立」に住んでいるクラスの数は、実際のところそれほど多くはありません。 通常、別のクラス(またはクラスのセット)でデータベース(またはサービス)を操作するためのすべてのロジックを正規化しても、遅かれ早かれ、これらのクラスを使用して高レベルの動作を取得する誰かが表示されます。次に、「テストする必要もあります。



しかし、最初に、データベースまたは外部サービスを操作するためのロジックと、このデータを処理するためのロジックが1箇所に集中している、より一般的なケースを見てみましょう。





//  ,     //    public class LoginViewModel { public LoginViewModel() { //     UserName = ReadLastUserName(); } //  ;     public string UserName { get; set; } //   UserName public void Login() { //      ,    // .         //  SaveLastUserName(UserName); } //      private string ReadLastUserName() { //  ,       ... //   -,     return "Jonh Doe"; } //     private void SaveLastUserName(string lastUserName) { //  ,   ,    } }
      
      







このようなクラスのテストに関しては、通常、このビューモデルはフォームに配置され、手作業でテストされます。サーバーコンポーネントの実装時にビューモデルの代わりにこのようなロジックの混合が発生する場合は、必要な高レベルを呼び出す単純なコンソールアプリケーションを作成してテストします機能、したがってモジュール全体をテストします。 どちらの場合も、このテストオプションを非常に自動で呼び出すことはできません。





「今日、こんなのがらくたを書けるのは誰?」と叫ぶ必要はありません。 結局のところ、そのようなアプローチの危険性についてはすでに多くのことが書かれており、実際、私たちは団結したshunitiと他のユーティリティを持っているので、これは20年前の非現実的なアコーディオンです!」 ちなみに、これはボタンアコーディオンですが、第一に、ユニットや他のコンテナに関するものではなく、基本原則に関するものであり、第二に、このような「統合」テストは、いずれにしても、私の「外国」のものの中で非常に人気があります同僚。



アプリケーションテスト用の継ぎ目の作成




私たちのビューモデルが違反しているいくつの新しいデザインの原則について考えなくても、そのデザインがやや残念だということは明らかです。 実際、古い祖父のブチェフの方法で設計したとしても、最後のユーザーの名前を保持するすべての作業、データベース(または他の外部データソース)を操作するロジックを視界から隠し、これを誰かの「問題」にする必要があることが明らかになりますまた、この「誰か」を「ビルディングブロック」として使用して、より高いレベルの動作を取得します。



 internal class LastUsernameProvider { //         public string ReadLastUserName() { return "Jonh Doe"; } //   ,       public void SaveLastUserName(string userName) { } } public class LoginViewModel { //          private readonly LastUsernameProvider _provider = new LastUsernameProvider(); public LoginViewModel() { //        UserName = _provider.ReadLastUserName(); } public string UserName { get; set; } public void Login() { //         //    _provider.SaveLastUserName(UserName); } }
      
      







今のところ、単体テストの作成は依然として困難ですが、 LastUsernameProviderクラスの実際の実装を「偽造」して必要な動作をシミュレートすることがいかに簡単かが明らかになります。 別のインターフェースでこのクラスのメソッドを選択するか、単にそれらを仮想にして継承者でオーバーライドするだけで十分です。 その後、必要なオブジェクトをビューモデルに「ねじ込む」だけです。







正直なところ、私はコードの「テスト容易性」のためだけに設計変更の大ファンではありません。 実践が示すように、通常のオブジェクト指向設計は既に十分に「テスト」されているか、そうするために最小限の身体の動きのみを必要とします。 このテーマに関する追加の考えは、 「理想的なアーキテクチャ」 という記事に記載されています



依存関係を「注入」するためにサードパーティのライブラリに頼らなくても、いくつかの簡単な方法で独自にこれを行うことができます。 必要な依存関係は、追加のコンストラクター、プロパティを介して渡すか、 ILastUsernmameProviderインターフェイスを返すファクトリメソッドを作成できます。



コンストラクターを使用したオプションを見てみましょう。これは非常にシンプルで人気があります(外部依存関係の数が少ない場合、うまく機能します)。



 //     internal interface ILastUsernameProvider { string ReadLastUserName(); void SaveLastUserName(string userName); } internal class LastUsernameProvider : ILastUsernameProvider { //         public string ReadLastUserName() { return "Jonh Doe"; } //   ,       public void SaveLastUserName(string userName) { } } public class LoginViewModel { private readonly ILastUsernameProvider _provider; //       public LoginViewModel() : this(new LastUsernameProvider()) {} // ""        "" internal LoginViewModel(ILastUsernameProvider provider) { _provider = provider; UserName = _provider.ReadLastUserName(); } public string UserName { get; set; } public void Login() { _provider.SaveLastUserName(UserName); } }
      
      







追加のコンストラクターは内部にあるため、このアセンブリー内およびユニットテストの「フレンドリ」アセンブリー内でのみ使用できます。 もちろん、テスト対象のクラスが内部クラスであれば、そうではありませんが、内部クラスのすべての「クライアント」は同じアセンブリ内にあるため、それらを制御するのは簡単です。 「偽の」動作を設定する内部メソッドの追加に基づくこのアプローチは、IoCコンテナーなどのより複雑な依存関係管理メカニズムの使用に制限を課すことなく、コードテストを簡素化する合理的な妥協案です。





インターフェイスを使用する際の欠点の1つは、読みやすさの低下です。これは、インターフェイスの実装の数と特定のインターフェイスメソッドの実装がどこにあるかが明確ではないためです。 Resharperなどのツールは、メソッド宣言へのナビゲーション(宣言へ移動)だけでなく、メソッドの実装へのナビゲーション(実装へ移動)もサポートしているため、この問題を大幅に軽減します。









ステータスチェックと動作チェック




次に、最後にログインしたユーザーの名前を受け取るLoginViewModelクラスのコンストラクターのユニットテストを作成し、次にLoginメソッドのユニットテストを作成して、最後のユーザーの名前を保存する必要があります。



これらのテストの通常の実装では、インターフェイスの「偽の」実装が必要です。最初のケースでは、 ReadLastUserNameメソッドで最後のユーザーの任意の名前を返す必要があり、2番目のケースではSaveLastUserNameメソッドが呼び出されることを確認してください



この点で、2つのタイプの「偽」クラスが異なりますスタブはテスト対象オブジェクトの望ましい状態を取得するように設計されmokasは テスト対象オブジェクト の予想される動作を確認するために使用されます



スタブはステートメントで使用されることはなく、テストクラスの外部環境のみをモデル化する単純な「サーバント」です。 同時に、テストクラスの状態は、スタブのインストール状態に依存するステートメントで検証されます。



 //       internal class LastUsernameProviderStub : ILastUsernameProvider { //   ,     //      public string UserName; //     -   UserName public string ReadLastUserName() { return UserName; } //         public void SaveLastUserName(string userName) { } } [TestFixture] public class LoginViewModelTests { //        - [Test] public void TestViewModelConstructor() { var stub = new LastUsernameProviderStub(); // ""   stub.UserName = "Jon Skeet"; // -!! var vm = new LoginViewModel(stub); //     Assert.That(vm.UserName, Is.EqualTo(stub.UserName)); } }
      
      







モックには別の役割があります。 モックはテストされたオブジェクトに「スリップ」しますが、必要な環境を作成するためではなく(この役割を果たすことはできます)、まず最初に、テストされたオブジェクトが必要なアクションを実行したことを確認します。 (このため、このタイプのテストは、 状態 ベースの テストに使用されるスタブとは異なり、 動作テストと呼ばれます )。



 //   ,   SaveLastUserName   //    internal class LastUsernameProviderMock : ILastUsernameProvider { //           public string SavedUserName; //          , //    ""    "" public string ReadLastUserName() { return "Jonh Skeet";} //          SavedUserName  public void SaveLastUserName(string userName) { SavedUserName = userName; } } // ,     Login      [Test] public void TestLogin() { var mock = new LastUsernameProviderMock(); var vm = new LoginViewModel(mock); //   -     vm.UserName = "Bob Martin"; //     Login vm.Login(); //   ,     SaveLastUserName Assert.That(mock.SavedUserName, Is.EqualTo(vm.UserName)); }
      
      







なぜこれらの違いについて知る必要があるのですか?




実際、特にそのような「偽物」を自分の手で実装する場合、概念の違いは取るに足らないように見えるかもしれません。 この場合、これらのパターンを知っていると、他の開発者と同じ言語を話せるようになり、偽のクラスの名前が簡単になります。



ただし、遅かれ早かれ、インターフェイスの手動実装のこの素晴らしいレッスンにうんざりする可能性があり、Rhino Mocks、Moq、Microsoft MolesなどのIsolationフレームワークのいずれかに注意を払うことになります。 そこで、これらの用語は必ず満たされ、これらのタイプの偽物の違いを理解することは非常に役立ちます。



これらのフレームワークはそれぞれ個別の記事にふさわしく、IMOはこれらの概念の理解を複雑にするだけなので、意図的にこれらのフレームワークには触れませんでした。 しかし、これらのフレームワークのいくつかをさらに詳細に見ることにまだ興味がある場合は、そのうちの1つである「Microsoft Moles」について詳しく説明しました。



All Articles