1

私は TDD (Asp.net MVC3 環境) の初心者であり、より優れた開発アプローチとして TDD を採用しようとしています。

本番コードには、次のシナリオがあります

ウェブで

//Autofac used to resolve Dependency
TestController(XService xSerivice,YSerivice yService) 
{_xService =xService,_YService= yService}

[HTTPPost]
ActionResult Create(A1 a1)
{
  _xService.XUnitOfWork.A1.add(a1)
  _xService.XUnitOfwork.SaveChanges();

} 

// ここで、X、Y は異なるコンテキストであり、具体的なクラスであり、インターフェースは実装されていません!

ビジネス層で

Xservice(XUnitofWork) // インターフェイスが実装されていません!

DALレイヤーで

'XUnitofWork:DataRepostory(Generic)...
    {
      GenericRepository<a1Entity> A1,
      GenericRepository<a2Entity> A2
    }

ここで、BAL レイヤーと Web レイヤーの両方にインターフェイスを実装する必要があることに気付きました。私の質問は、コントローラーでサービス (XService、YService) をモックしていくつかの動作 (TDD) をテストする方法はあります _xService.XUnitOfwork.SaveChanges()か?

助けてください。よろしくお願いします!

4

3 に答える 3

2

具象クラスのメンバー(プロパティ、メソッド)を としてマークするとvirtual、それらのメソッド/プロパティを個別にモックできると思います。(仮想に相当する VB はオーバーライド可能だと思います..?)

Moq は、テストの実行時に実行時に何かの新しい具体的な実装を作成することによって機能します。これが、インターフェイスと抽象クラスでうまく機能する理由です。ただし、インターフェイスまたは抽象クラスがない場合は、メソッドまたはプロパティをオーバーライドする必要があります。

質問の著者の回答への返信:

あなたは自称 TDD 初心者なので、クラスをテスト可能にするためだけにパラメーターなしのコンストラクターをクラスに追加することは、受け入れられる解決策ではないことを指摘したかっただけです。

GenericRepository クラスに Entity Framework の DbSet / IDbSet への強い依存関係を与えることで、リポジトリの実装と EF の間に緊密な結合が作成されます...using System.Data.Entityそのファイルの上部にある行に注意してください。

コンストラクターの依存関係を追加する場合はいつでも、それをインターフェイスまたは抽象クラスとして追加することを真剣に検討する必要があります。制御していないライブラリのメンバー (EF の DbContext など) にアクセスする必要がある場合は、Morten の回答に従って、独自のカスタム インターフェイスで機能をラップします。

DbContext の場合、このクラスは UnitOfWork 実装を提供するだけではありません。また、データをクエリして、リポジトリ内のアイテムを追加/置換/削除する方法も提供します。

public interface IUnitOfWork
{
    int SaveChanges();
}

public interface IQuery
{
    IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class;
}

public interface ICommand : IQuery
{
    void Add(object entity);
    void Replace(object entity);
    void Remove(object entity);
}

次のように、これら 3 つのインターフェイスで DbContext を簡単にラップできます。

public class MyCustomDbContext : DbContext, IUnitOfWork, ICommand
{
    // DbContext already implements int SaveChanges()

    public IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class
    {
        return this.Set<TEntity>();
    }

    public void Add(object entity)
    {
        this.Entry(entity).State = EntityState.Added;
    }

    public void Replace(object entity)
    {
        this.Entry(entity).State = EntityState.Modified;
    }

    public void Remove(object entity)
    {
        this.Entry(entity).State = EntityState.Deleted;
    }
}

インターフェイスが に依存しないことに注意してくださいSystem.Data.Entity。これらは、プリミティブと、 、 、 などの標準の .NET 型を使用objectIQueryable<T>ますint。このようにして、汎用リポジトリの依存関係をインターフェイスに与えると、System.Data.Entity への依存関係を削除できます。

// using System.Data.Entity; // no need for this dependency any more

public class GenericRepository
{
    private readonly ICommand _entities;
    private readonly IQueryable<TEntity> _queryable;

    public GenericRepository(ICommand entities)
    {
        this._entities = entities;
        this._queryable = entities.GetQueryable<TEntity>();
    }

    //public GenericRepository()
    //{ 
        // no need for a parameterless constructor!
    //}
}

...そして、GenericRepository は完全に単体テスト可能になりました。これは、これらのインターフェイス メソッドを簡単にモックできるためです。

最終的な注意:

また、あなた自身の質問に対する回答を見た後、あなたの UnitOfWork クラスのプロパティとして CompanyRepository があるようです。次に、UnitOfWork を依存関係として CompanyInformationController に注入します。これは逆です。代わりに、CompanyRepository (またはそのインターフェイス) をコントローラーのコンストラクターに挿入する必要があります。UnitOfWork パターンは、既知のリポジトリの参照を維持することとは関係ありません。関連するアイテムに加えられた複数の変更を追跡して、それらをすべて 1 つのトランザクションとして一度にプッシュできるようにすることです。EF はこれを自動的に行うため、アプリが IQuery、ICommand、または IUnitOfWork の実装を要求するかどうかに関係なく、AutoFac が同じ DbContext インスタンスを提供している限り、UnitOfWork が関係する唯一のメソッドは SaveChanges() です。

于 2012-06-01T18:59:34.087 に答える
0

最善の方法は、XService のインターフェースを作成することです。何らかの理由でそれが不可能な場合 (XService がインターフェースを実装しないサードパーティ クラスである場合)、インターフェースを持つラッパー クラスで機能をラップすることを検討してください。

于 2012-06-04T07:59:42.743 に答える
0

お返事をありがとうございます。私がやろうとしていたテストは、数時間かけて成功し、以前のコードを変更しました。変更点は次のとおりです。1) 冗長サービスの代わりに、コントローラーで UnitofWork を使用するようになりました。2) パラメーターのないコンストラクターを GenericRepository クラスに追加しました (DBContext なしで!)。これは、コンストラクターのパラメーターとして DBContext が必要になるためです。

GenericRepository: public class GenericRepository where TEntity : class {

    internal DbContext _context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(DbContext context)
    {
        this._context = context;
        this.dbSet = context.Set<TEntity>();
    }
    public GenericRepository()    //newly added!
    { 

    }

...............

完全なテスト

    [TestMethod]
    public void Index_Return_OneModel_WhenCalling()
    {
        //arrange

        AutoMapperExtension automapper = new AutoMapperExtension();          
        var moqentities = new Mock<SetupEntities>();                
        List<CompanyInformation> list =new List<CompanyInformation>();
        list.Add(new CompanyInformation{ CompanyName = "a", CompanyAddress = "aa", Id = 1});
        list.Add(new CompanyInformation { CompanyName = "b", CompanyAddress = "b", Id = 2 });                 
        var unitOfWork = new Mock<UnitOfWork>(moqentities.Object);
        unitOfWork.Setup(d => d.CompanyRepository).Returns(new GenericRepository<CompanyInformation>());
        unitOfWork.Setup(d => d.CompanyRepository.GetAll()).Returns(list.AsQueryable());

        var controller = new CompanyInformationController(unitOfWork.Object);          

        //Act
        var result =(ViewResult) controller.Index();
        var model =(CompanyInformationViewModel)  result.ViewData.Model;

        //Assert
        Assert.AreEqual(1, model.Id);           

    } 
于 2012-06-02T11:13:04.563 に答える