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 等价物是 Overridable..?)

Moq 通过在测试运行时在运行时创建新的具体实现来工作。这就是为什么它与接口和抽象类一起工作得如此好。但是如果没有接口或抽象类,则需要重写一个方法或属性。

回复问题作者的回答:

由于您自称是 TDD 新手,我只想指出,仅仅为了使类可测试而向类添加无参数构造函数不应该是一个可接受的解决方案。

通过为您的 GenericRepository 类提供对实体框架的 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);
}

您可以很容易地将 DbContext 包装在这 3 个接口中,如下所示:

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 现在是完全可单元测试的,因为您可以轻松地模拟任何这些接口方法。

最后说明:

此外,在看到您对自己问题的回答后,您似乎将 CompanyRepository 作为 UnitOfWork 类的属性。然后,您将 UnitOfWork 作为对 CompanyInformationController 的依赖项注入。这是倒退。相反,您应该将 CompanyRepository(或其接口)注入控制器的构造函数。UnitOfWork 模式与维护已知存储库的引用无关。它是关于跟踪对相关项目所做的多项更改,以便它们都可以作为单个事务推送一次。EF 会自动执行此操作,因此只要 AutoFac 提供相同的 DbContext 实例,无论您的应用程序请求 IQuery、ICommand 还是 IUnitOfWork 实现,那么 UnitOfWork 应该关注的唯一方法是 SaveChanges()。

于 2012-06-01T18:59:34.087 回答
0

感谢您的回复。在花了几个小时并更改了我以前的代码后,我试图做的测试是成功的。更改如下: 1) 现在在我的控制器中使用 UnitofWork 而不是冗余服务。2) 向 GenericRepository 类添加了一个无参数构造函数。(没有任何 DBContext!),因为它将需要一个 DBContext 作为构造函数中的参数,不能通过提供模拟 DBContext 来替代。

GenericRepository: 公共类 GenericRepository 其中 TEntity :类 {

    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 回答
0

最好的方法是为 XService 创建一个接口。如果由于某种原因这是不可能的(如果 XService 是不实现接口的第三方类),则考虑将功能包装在具有接口的包装类中。

于 2012-06-04T07:59:42.743 回答