7

私は単体テストに飛び込み始めたばかりで、リポジトリパターンとIoCを把握し始めたところです。しかし、その一部が少しばかげているように見えるので、私はそれを完全には理解していないと思います。説明させてください。

私のコントローラー:

public class UserProfileController : ApiController
{
    private IUserProfileRepository repository;

    // Optional constructor, passes repository, allows dependency injection
    public UserProfileController(IUserProfileRepository userProfileRepository)
    {
        this.repository = userProfileRepository;
    }

    // GET api/UserProfile
    // Returns a list of all users
    public IEnumerable<UserProfile> Get()
    {
        // Only Admins can see a list of users
        if (Roles.IsUserInRole("Admin"))
        {
            return repository.Get();
        }
        else
        {
            throw new HttpResponseException(
                new HttpResponseMessage(HttpStatusCode.Forbidden)
                {
                    ReasonPhrase = "Administrator access required"
                });
        }
    }

// Other methods, etc.

(私には依存関係Roles.IsUserInRole( "Admin")があり、抽象化する方法がわからないため、いくつかの問題が発生することに注意してください)。

私の典型的なリポジトリインターフェイス:

public interface IUserProfileRepository : IDisposable
{
    IEnumerable<UserProfile> Get();
    // Other methods, etc.
}

レポ:

public class UserProfileRepository : IUserProfileRepository, IDisposable
{
    private OfootContext context;

    public UserProfileRepository(OfootContext context)
    {
        this.context = context;
    }

    public IEnumerable<UserProfile> Get()
    {
        return context.UserProfiles.AsEnumerable();
    }

// ... More code

したがって、すべてが正常に見えます。ビジネスロジックからビジネスアクセス層を抽象化し、単体テストを実行するための偽のリポジトリを作成できるようになりました。

偽のレポ:

public class FakeUserProfileRepository : IUserProfileRepository, IDisposable
{
    private List<UserProfile> context;

    public FakeUserProfileRepository(List<UserProfile> context)
    {
        this.context = context;
    }

    public IEnumerable<UserProfile> Get()
    {
        return context.AsEnumerable();
    }

およびテスト:

[TestMethod]
public void GetUsers()
{
    // Arrange
    var items = new List<UserProfile>()
    {
        new UserProfile
        {
            UserId = 1,
            Username = "Bob",
        },
        new UserProfile
        {
            UserId = 2,
            Username = "Bob2",
        }
    };

    FakeUserProfileRepository repo = new FakeUserProfileRepository(
        items);
    UserProfileController controller = new UserProfileController(
        repo);

    // Act
    IEnumerable<UserProfile> result = controller.Get();

    // Assert
    Assert.IsNotNull(result);
}

同じページにいるので(そして「コードの臭い」を自由に指摘してください)、ここに私の考えがあります:

  1. 偽のリポジトリでは、Entity Frameworkロジックをすべて再実装し、Listオブジェクトを処理するように変更する必要があります。これは、デバッグする必要があるチェーン内のより多くの作業とより多くのリンクです。
  2. 単体テストに合格した場合、EFにアクセスするコードについては何も表示されないため、アプリケーションが失敗する可能性があります。これは、EFコードを個別にテストして、データベースにヒットさせる必要があることを意味します。
  3. #1から、単体テストがEFコードをテストしていない場合は、コントローラーでの認証、承認、およびユーザー作成コードを処理しているだけです。WebSecurityクラスとRolesクラスが私のデータベースにヒットするため、これは不可能です。
  4. データベースを使用してEFコード(ポイント#2)をテストし、コントローラーコード(認証と承認、ポイント#3)をテストするデータベースが必要な場合は、リポジトリを使用して抽象化する必要があります。(IContextなどを使用して)コンテキストを抽象化し、DropCreateDatabaseAlwaysクラスを使用して入力されたテストデータベースに接続しないのはなぜですか?

ユーザーアカウントのガベージを抽象化する方法を見つけたとしても、コードをシャッフルして、コンテキストを置き換えることができるコードをさらに作成しています(おそらく、2倍になるかもしれません。偽物を作成する必要があるため)。

私の質問は:私は何が欠けていますか?それは全体的なコンセプトですか、それとも特定のものですか?

4

1 に答える 1

12

あなたは正しい方向に進んでいます。物事を立ち上げて実行するのは常に苦痛ですが、将来的には報われるでしょう。

「偽の」オブジェクトを作成するのではなく、Moqのようなフレームワークをお勧めします。これにより、インターフェイス全体を再実装するのではなく、テスト時に必要な動作を設定できます。たとえば、テストでは次のように書くことができます。

    Mock<IUserProfileRepository> mockUserRepo = new Mock<IUserProfileRepository>();
    var items = new List<UserProfile>()
    {
        new UserProfile
        {
            UserId = 1,
            Username = "Bob",
        },
        new UserProfile
        {
            UserId = 2,
            Username = "Bob2",
        }
    };
   mockUserRepo.Setup(m => m.Get().Returns(items.AsEnumerable());
   UserProfileController controller = new UserProfileController(
        mockUserRepo.Object);

    // Act
   IEnumerable<UserProfile> result = controller.Get();
   //Now you can keep varying the mock response by changing the Setup(), so now 
   //check for null response handling, 0 items, exceptions etc...

このすべての努力の最終的な結果は、テストをコントローラーに完全に分離し、DBの依存関係がなく、クラスを作成せずに入力を簡単に変更できることです。

この単純なアーキテクチャパターンに従うと、優れたテスト容易性と関心の分離が得られます。システムがより複雑になるにつれて、UnityのようなDIコンテナーを利用できます。

認証部分では、ASP.Net MVCが使用する例として[Authorization(Roles = "Admin")]のように、メソッドを装飾できる属性を作成することをお勧めします。これにより、Auth関連のものをコントローラーのビジネスロジックから切り離したままにする、もう1つの便利な分野横断的なパターンが作成されます。

于 2013-01-26T00:10:54.590 に答える