C# 如何对数据库访问逻辑进行单元测试?

C# 如何对数据库访问逻辑进行单元测试?,c#,sql-server,entity-framework,unit-testing,ef-code-first,C#,Sql Server,Entity Framework,Unit Testing,Ef Code First,我有一个基于EF-code-First的项目,使用MSSQL服务器。在我的一个存储库中,我正在使用PredicateBuilder动态构建查询。每次代码中发生更改时手动测试所有可能的结果,这非常耗时 出于这个原因,我想通过单元测试来实现自动化。我正在考虑将sqlcompact用于单元测试,将mssqlserver用于生产。但是如何为sqlcompactserver启用迁移呢 这是dbContext类: public partial class ApplicationDbContext :

我有一个基于
EF-code-First
的项目,使用
MSSQL服务器
。在我的一个存储库中,我正在使用
PredicateBuilder
动态构建查询。每次代码中发生更改时手动测试所有可能的结果,这非常耗时

出于这个原因,我想通过单元测试来实现自动化。我正在考虑将
sqlcompact
用于单元测试,将
mssqlserver
用于生产。但是如何为
sqlcompact
server启用迁移呢

这是dbContext类:

public partial class ApplicationDbContext :
        IdentityDbContext<ApplicationUser, ApplicationRole, int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext() : base("name=DefaultConnection") { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());
        base.OnModelCreating(modelBuilder);
     }
}
public分部类ApplicationDbContext:
IdentityDbContext
{
public ApplicationDbContext():base(“name=DefaultConnection”){}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
SetInitializer(新的MigrateDatabaseToLatestVersion());
基于模型创建(modelBuilder);
}
}
存储库:

public class FilterRepository : IFilterRepository
{
    private ApplicationDbContext _dbContext;

    public FilterRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
        DbInterception.Add(new CommandInterceptor());
    }

    public IEnumerable<Person> GetPersons(Filter filter)
    {
        try
        {
            var persons = PredicateBuilder.False<Person>();

            _dbContext.Configuration.AutoDetectChangesEnabled = false;

            var result = _dbContext.Persons
                            .AsNoTracking() 
                            .Where(persons)
                            .OrderBy(x => x.Name)
                            .Skip(filter.Skip)
                            .Take(10)
                            .ToList();

            _dbContext.Configuration.AutoDetectChangesEnabled = true;

            return result;
        }
        catch (Exception ex)
        {

        }
    }
}
公共类过滤器存储库:IFilterRepository
{
私有应用程序dbContext_dbContext;
公共筛选器存储库(ApplicationDbContext dbContext)
{
_dbContext=dbContext;
添加(newcommandinterceptor());
}
公共IEnumerable GetPersons(筛选器)
{
尝试
{
var persons=PredicateBuilder.False();
_dbContext.Configuration.AutoDetectChangesEnabled=false;
var result=\u dbContext.Persons
.AsNoTracking()
.何处(人)
.OrderBy(x=>x.Name)
.Skip(filter.Skip)
.Take(10)
.ToList();
_dbContext.Configuration.AutoDetectChangesEnabled=true;
返回结果;
}
捕获(例外情况除外)
{
}
}
}

好的,所以切换到不同的数据库进行测试(imo)是个坏主意

这里有一些术语,可以帮助您更好地组织测试

单元测试用于测试您的业务功能,通常您只需为此模拟数据,因为您希望测试域类中的输入(数据)发生了什么,而不是它们来自何处

集成测试用于测试业务域层如何与服务层交互(数据层是服务层),我还将测试查询的正确性视为运行查询的集成测试

因此,在您的情况下,不要混淆事情,并可能添加由于两个数据库工作方式不同而导致的意外行为。如果您正在测试业务功能,请为此模拟数据。一开始速度更快,您将进行测试,这正是您想要测试的

通过集成测试,您正在测试您与服务层的交互是否正确,在这种情况下,您的测试并不是真正测试业务逻辑是否工作,这应该在单元测试中涵盖;测试您的查询是否为其谓词返回正确的数据,以及要持久化的任何数据是否正确持久化。此外,任何正在进行的事务都按照您的预期工作。场景的端到端测试也是有效的集成测试

您绝对需要在与产品相同的数据库平台上执行此操作,不要期望SQL Compact和SQL Server的行为方式相同

编辑…以获取评论

因此,模拟存储库调用的正常方法是使用依赖项注入,您不必这样做,但它更简单,而且是最佳实践

其思想是,在使用数据的域类中,首先从DI容器中获取存储库或查询类,该容器要么注入构造函数,要么从DI容器中提取

// in your domain class you would have something like...
    var repo = container.Get<IRepository>();
    var myList = repo.GetMyObjects(predcate);
//在您的域类中,您会有类似于。。。
var repo=container.Get();
var myList=repo.GetMyObjects(predcate);
因此,有了Moq,你现在可以简单地嘲笑这个电话了

//Where you do your container registration..
var repo = Mock<IRepository>
repo.Setup( o => o.GetMyObject(predecate)).Returns( (predecate) => <your dummy list>));
container.Register(repo.Object);
// Then later on your business domain object gets the dummy repo instead.
//在哪里进行容器注册。。
var回购=模拟
repo.Setup(o=>o.GetMyObject(predecate)).Returns((predecate)=>);
容器登记簿(回购对象);
//随后,您的业务域对象将获得虚拟repo。

请注意,这是伪代码,根据使用的DI和模拟框架略有不同。

请参阅。简言之,创建一个初始值设定项,删除并重新创建压缩数据库,然后对其进行种子设定。在单元测试开始时初始化上下文,每次运行测试时都会重新创建上下文。但是,如果您想访问数据库,这更像是一个集成测试而不是单元测试。如果我模拟数据,如何将模拟的数据传递到我的存储库?请在一分钟后或1分钟后查看我的更新答案以了解存储库代码。模拟存储库将返回为给定测试设置的数据。请参见以下内容:。也就是说,您可以配置mock,以便对
GetPersons
的调用可以对您的测试数据进行操作。@Tim Jarvis,我一直在内存中创建数据库,我发现了以下博文:。他们为dbcontext创建了一个接口来填充内存中的数据库。“这篇文章作为参考是不是很好?”Quoter说实话,如果你发现自己试图把dbContext放到内存中,我会质疑你的测试。老实说,我认为这是个坏主意……事实上,在这种情况下,您将针对Linq to对象而不是Linq to EF执行Linq-它们的行为不同-就我而言,这可能会使测试无效。@Quoter说,我的理解是EF 7.0将为内存中的数据提供支持。-我想你可以下载测试版。因此,如果你真的坚持这样做…它可能与下一个版本。