6

最新のASP.NETMVCプロジェクトを構築しながら、単体テスト、依存関係の注入、およびそのすべてのジャズに取り組み始めています。

私は今、コントローラーの単体テストを行いたいと思っていますが、IoCコンテナーなしでこれを適切に行う方法を理解するのに苦労しています。

単純なコントローラーを例にとってみましょう。

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository = new SqlQuestionsRepository();

    // ... Continue with various controller actions
}

このクラスは、SqlQuestionsRepositoryを直接インスタンス化するため、ユニットテストはあまりできません。それでは、ディペンデンシーインジェクションルートをたどって実行しましょう。

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository;

    public QuestionsController(IQuestionsRepository repository)
    {
        _repository = repository;
    }
}

これは良いようです。これで、モックIQuestionsRepositoryを使用して単体テストを簡単に作成できます。しかし、コントローラーをインスタンス化するにはどうすればよいでしょうか。コールチェーンのさらに上のどこかで、SqlQuestionRepositoryをインスタンス化する必要があります。問題を解決するのではなく、単に問題を別の場所に移したようです。

さて、これはIoCコンテナーがコントローラーの依存関係を配線することで役立つと同時に、コントローラーを簡単にユニットテスト可能に保つ良い例であることを私は知っています。

私の質問は、 IoCコンテナを使用せずに、この種のものの単体テストをどのように行うと想定するかということです。

注:私はIoCコンテナーに反対しているわけではなく、まもなくその道を進むでしょう。しかし、私はそれらを使用しない人々のための代替手段が何であるか興味があります。

4

4 に答える 4

2

フィールドの直接インスタンス化を維持し、セッターも提供することはできませんか? この場合、単体テスト中にのみセッターを呼び出すことになります。このようなもの:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository = new SqlQuestionsRepository();

    // Really only called during unit testing...
    public QuestionsController(IQuestionsRepository repository)
    {
        _repository = repository;
    }
}

私は .NET にはあまり詳しくありませんが、Java の補足として、これは既存のコードをリファクタリングしてテスト容易性を向上させる一般的な方法です。IE、既に使用されているクラスがあり、既存の機能を壊さずにコード カバレッジを改善するためにそれらを変更する必要がある場合。

私たちのチームは以前にこれを行っており、通常、setter の可視性を package-private に設定し、setter を呼び出せるようにテスト クラスのパッケージを同じに保ちます。

于 2009-06-25T02:40:25.777 に答える
2

ある種のデフォルトの動作を持つコントローラーにデフォルトのコンストラクターを設定できます。

何かのようなもの...

public QuestionsController()
    : this(new QuestionsRepository())
{
}

そうすれば、コントローラー ファクトリがコントローラーの新しいインスタンスを作成するときに、既定のコンストラクターの動作が使用されます。次に、単体テストで、モック フレームワークを使用して、モックを他のコンストラクターに渡すことができます。

于 2009-06-25T02:41:10.520 に答える
1

1 つのオプションは、偽物を使用することです。

public class FakeQuestionsRepository : IQuestionsRepository {
    public FakeQuestionsRepository() { } //simple constructor
    //implement the interface, without going to the database
}

[TestFixture] public class QuestionsControllerTest {
    [Test] public void should_be_able_to_instantiate_the_controller() {
        //setup the scenario
        var repository = new FakeQuestionsRepository();
        var controller = new QuestionsController(repository);
        //assert some things on the controller
    }
}

もう 1 つのオプションは、モックとモッキング フレームワークを使用することです。これにより、これらのモックをその場で自動生成できます。

[TestFixture] public class QuestionsControllerTest {
    [Test] public void should_be_able_to_instantiate_the_controller() {
        //setup the scenario
        var repositoryMock = new Moq.Mock<IQuestionsRepository>();
        repositoryMock
            .SetupGet(o => o.FirstQuestion)
            .Returns(new Question { X = 10 });
        //repositoryMock.Object is of type IQuestionsRepository:
        var controller = new QuestionsController(repositoryMock.Object);
        //assert some things on the controller
    }
}

すべてのオブジェクトが構築される場所について。単体テストでは、オブジェクトの最小限のセットのみを設定します。つまり、テスト対象の実際のオブジェクトと、テスト対象の実際のオブジェクトが必要とするいくつかの偽造またはモック化された依存関係です。たとえば、テスト対象の実際のオブジェクトは のインスタンスでQuestionsControllerあり、依存関係があるため、最初の例のようなIQuestionsRepositoryフェイクIQuestionsRepositoryまたは 2 番目の例のようなモックのいずれかを指定しIQuestionsRepositoryます。

ただし、実際のシステムでは、コンテナー全体をソフトウェアの最上位レベルでセットアップします。たとえば、Web アプリケーションでは、コンテナをセットアップし、すべてのインターフェイスと実装クラスをGlobalApplication.Application_Start.

于 2009-06-25T02:45:52.397 に答える
0

私はピーターの答えを少し広げています。

多くのエンティティ タイプを持つアプリケーションでは、コントローラが複数のリポジトリやサービスなどへの参照を必要とすることは珍しくありません。テスト コードでこれらすべての依存関係を手動で渡すのは面倒だと思います (特に、特定のテストに 1 つまたは 2 つの依存関係しか含まれない場合があるため)。これらのシナリオでは、コンストラクター注入よりもセッター注入スタイルの IOC を好みます。私がこれを使用するパターン:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository Repository 
    {
        get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); }
        set { _repo = value; }
    }
    private IQuestionsRepository _repo;

    // Don't need anything fancy in the ctor
    public QuestionsController()
    {
    }
}

IoC.GetInstance<>特定の IOC フレームワークが使用する構文に置き換えます。

実稼働環境ではプロパティ セッターは呼び出されないため、最初にゲッターが呼び出されると、コントローラーが IOC フレームワークを呼び出し、インスタンスを取得して保存します。

テストでは、コントローラー メソッドを呼び出す前にセッターを呼び出すだけです。

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
}

このアプローチの利点、私見:

  1. 本番環境では引き続き IOC を利用します。
  2. テスト中にコントローラーを手動で簡単に構築できます。テストで実際に使用する依存関係を初期化するだけで済みます。
  3. 共通の依存関係を手動で構成したくない場合は、テスト固有の IOC 構成を作成できます。
于 2010-01-08T15:15:11.293 に答える