设计一个可以单元测试的类

Gaz*_*azB 3 asp.net asp.net-mvc unit-testing ninject asp.net-mvc-3

我正在阅读Apress ASP.NET MVC 3书,并试图确保我为一切可能创建单元测试但是在花了一天的时间试图找出为什么编辑不会保存(参见这个问题)我想要为此创建一个单元测试.

我已经知道我需要为以下类创建一个单元测试:

public class EFProductRepository : IProductRepository {
    private EFDbContext context = new EFDbContext();

    public IQueryable<Product> Products {
        get { return context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            context.Products.Add(product);
        }
        context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        context.Products.Remove(product);
        context.SaveChanges();
    }
}

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Ninject.MVC3和Moq并且之前已经创建了几个单元测试(虽然正在处理前面提到过的书),所以我慢慢地了解它.我已经(希望正确地)创建了一个构造函数方法,使我能够传入_context:

public class EFProductRepository : IProductRepository {
    private EFDbContext _context;

    // constructor
    public EFProductRepository(EFDbContext context) {
        _context = context;
    }

    public IQueryable<Product> Products {
        get { return _context.Products; }
    }

    public void SaveProduct(Product product) {
        if (product.ProductID == 0) {
            _context.Products.Add(product);
        } else {
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }

    public void DeleteProduct(Product product) {
        _context.Products.Remove(product);
        _context.SaveChanges();
    }
}
Run Code Online (Sandbox Code Playgroud)

但这是我开始遇到麻烦的地方......我相信我需要创建一个接口EFDbContext(见下文)所以我可以用模拟仓库代替它来测试它但是它建立在类上DbContext:

public class EFDbContext : DbContext {
    public DbSet<Product> Products { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

System.Data.Entity我和我不能为我的生活找出如何为它创建一个接口...如果我创建以下接口我得到错误,因为缺少.SaveChanges()来自DbContext类的方法,我无法建立使用"DbContext"的接口,如`EFDbContext,因为它是一个类而不是接口...

using System;
using System.Data.Entity;
using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Concrete {
    interface IEFDbContext {
        DbSet<Product> Products { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

原始Source可以从这个页面上的"源代码/下载"中获取包含我在上面的代码片段中遗漏了一些内容(或者只是询问并且我将添加它).

我达到了我所理解的极限,无论我搜索或阅读的内容,我似乎无法弄清楚我是如何理解的.请帮忙!

Jes*_*ick 6

这里的问题是你没有足够的抽象.抽象/接口的要点是定义一个以技术无关的方式暴露行为的契约.

换句话说,为EFDbContext创建接口是一个很好的第一步,但该接口仍然依赖于具体实现--DbSet(DbSet).

快速解决此问题是将此属性公开为IDbSet而不是DbSet.理想情况下,你会暴露出比IQueryable更抽象的东西(虽然这不会给你Add()方法等).越抽象,越容易嘲笑.

然后,您将完成您所依赖的"合同"的其余部分 - 即SaveChanges()方法.

您更新的代码如下所示:

public class EFProductRepository : IProductRepository {
    private IEFDbContext context;

    public EFProductRepository(IEFDbContext context) {
        this.context = context;
    }
    ...
}

public interface IEFDbContext {
   IDbSet<Product> Products { get; set; }
   void SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

但是...... 你要问的主要问题是:你想要测试什么(相反,你想要嘲笑/避免测试的是什么)?换句话说:您是在尝试验证应用程序在保存某些内容时的工作方式,还是在测试实际的保存时间.

如果您只是测试应用程序的工作方式而不关心实际保存到数据库,我会考虑在更高级别进行模拟 - IProductRepository.然后你根本就没有打到数据库.

如果你想确保你的对象实际上被持久化到数据库,那么你应该点击DbContext并且毕竟不想模仿那个部分.

就个人而言,我认为这两种情况都是不同的 - 同样重要 - 我为每种情况编写单独的测试:一种测试我的应用程序执行它应该做的事情,另一种测试数据库交互是否有效.