【问题标题】:Entity Framework 5 - How to change connection string for unit testing?Entity Framework 5 - 如何更改单元测试的连接字符串?
【发布时间】:2013-09-13 08:28:12
【问题描述】:

这是我第一次涉足实体框架,我有一个使用 EF5 和存储库模式的工作项目。我想对实时数据库进行集成测试。我为我现有的生产数据库制作了一个快照,并编写了一个存储过程来在每次我想运行测试时重新创建一个新的快照。我的问题是如何在“单元测试模式”时将我的上下文切换到这个数据库快照?在我的 app.config 中,我既有实时连接字符串,也有测试连接字符串:

<connectionStrings>
  <add name="ReportingDbContext" connectionString="Server=LiveServer;Database=UnifiedReporting;User Id='myuser';Password='mypass';Trusted_Connection=False" providerName="System.Data.SqlClient" />
  <add name="TestingDbContext" connectionString="Server=LiveServer;Database=UnifiedReportingSnapshot;User Id='myuser';Password='mypass';Trusted_Connection=False" providerName="System.Data.SqlClient" />
</connectionStrings>

就目前而言,我的 DbContext 包含我想要使用的实体,如下所示:

public class ReportingDbContext : DbContext
{
    public ReportingDbContext() : base("name=ReportingDbContext") // as per my app.config
    {

    }

    // inventory
    public DbSet<ComputerEntity> Computers { get; set; }
    public DbSet<NetworkAdapterEntity> NetworkAdapters { get; set; }
    // ... plus a whole bunch more
}

我认为我需要做的是将 base("name=ReportingDbContext") 更改为 ("name=TestingDbContext"),但考虑到我的 Repository/UnitOfWork 设置,我不知道我该怎么做.这个问题可能在我的 UnitOfWork 中:

public interface IUnitOfWork : IDisposable
{
    void Commit();

    // inventory
    IRepository<ComputerEntity> Computers { get; }
    IRepository<NetworkAdapterEntity> NetworkAdapters { get; }
    // ... plus a bunch more
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ReportingDbContext _dbContext = null;

    public UnitOfWork()
    {
        _dbContext = new ReportingDbContext();
    }

    public void Commit()
    {
        _dbContext.SaveChanges();
    }

    // Inventory
    public IRepository<ComputerEntity> Computers {get { return new Repository<ComputerEntity>(_dbContext); }}
    public IRepository<NetworkAdapterEntity> NetworkAdapters { get { return new Repository<NetworkAdapterEntity>(_dbContext); } }
    // ... lots more
}

这个 UnitOfWork 很棒的是,我可以对我的所有存储库做一堆东西,并一次性保存它,而无需使用一堆上下文来同步。它可能与这个问题相关,也可能不相关,但这就是我的 UnitOfWork 使用存储库的方式。只有 1 个存储库类,但它可以提供所需的任何实体类型:

public interface IRepository<T> where T : class
{
    IQueryable<T> GetAll();
    IQueryable<T> Find(Expression<Func<T, bool>> predicate);
    T GetById(int id);
    void Remove(T entity);
    void Add(T newEntity);
}

public class Repository<T> : IRepository<T> where T : class
{
    protected DbContext DbContext { get; set; }
    protected DbSet<T> DbSet { get; set; }

    public Repository(DbContext dbContext)
    {
        if (dbContext == null)
        {
            throw new ArgumentNullException("dbContext");
        }
        DbContext = dbContext;
        DbSet = DbContext.Set<T>();
    }

    public IQueryable<T> GetAll()
    {
        return DbSet;
    }

 // ... more implementation of the interface, nothing fancy
}

使用这个魔法的端点在我的 WCF 服务中。这是我想要实际运行集成测试的地方。我的服务中的一个特定方法初始化了一个工作单元并使用它来做事。 UnitOfWork 在新建时会创建一个 ReportingDbContext,而此 ReportingDbContext 又引用“name=ReportingDbContext”的连接字符串。经过大量阅读,我认为答案是使用 Unity 或 Ninject 之类的 IoC 容器(以前没有使用过,但我想使用),而我一直坚持如何在这种情况下实现 IoC。这是我在 WCF 服务中使用的示例方法,它似乎对实时数据库连接字符串进行了硬编码:

public ComputerDTO GetComputerDetails(string hostname, string client)
{
 // don't worry about the return type, it's defined elsewhere
    using (var uoW = new UnitOfWork())
    {
        var repo = uoW.Computers;
        var computer = repo.Find(x => x.Hostname == hostname && x.CompanyEntity.Name == client).FirstOrDefault();
        // do stuff
    }
}

如果可能的话,我想将我的连接字符串保留在我的 app.config 中,并且能够在我对 WCF 服务中的方法进行 NUnit 测试的 [SetUp] 部分期间以某种方式切换到测试连接字符串。

【问题讨论】:

  • 如果您使用的是存储库模式,为什么不直接模拟数据库?
  • 在阅读了一堆关于 EF5 的 SO 问题/答案后,我读到嘲笑可能不是最好的方法。这是因为具有真实 DB 的 Linq-to-Entities 与具有假 DB 的 Linq-to-Objects 的行为不同。内存中的 DbSet 可能会顺利通过,但实际操作会失败。
  • 我想这取决于。如果您的存储库隐藏了 EF,那么如果您 测试存储库代码本身也没关系。如果您正在测试存储库代码,那么您可能需要一个真正的数据库。
  • 我总是使用带有自己的 app.config 的单独的单元测试项目。上下文在两者中具有相同的名称。
  • Gert - 如果我在我的测试项目中使用具有相同名称的不同连接字符串,是否会从测试项目中启动我的 ReportingDbContext 引用本地 app.config?

标签: wcf unit-testing entity-framework-5 ioc-container


【解决方案1】:

我总是使用带有自己的 App.config 的单独的单元测试项目。连接字符串与主应用程序中的名称相同,但数据库连接不同。

当您运行单元测试时,例如在 Visual Studio 中,在后台执行单元测试运行程序,它只是一个具有自己的配置 app.config 的常规应用程序。

您可以为每个测试启动和处置上下文。大多数单元测试框架具有将方法标记为设置/拆卸夹具的属性,可以按测试夹具或每个测试运行。您可以在测试装置设置(NUnit 中的[TestFixtureSetUp])和测试设置中的上下文(NUnit 中的[SetUp])中初始化 IoC 容器。

对于某些场景,我们使用脚本来确保和恢复数据库状态,但对于大多数测试,我们在测试设置中启动 TransactionScope 并在测试拆解中处理它(不提交)。这可以方便地回滚测试中所做的任何更改,但测试中所做的数据库更改是真实的。

【讨论】:

  • 谢谢格特!为确保我正确理解 TransactionScope,即使我调用 .SaveChanges() 正确,也不会保存我对 EF 上下文/模型的更改吗?数据库事务是一个完全独立的东西,将被提交,因为数据库不知道 C# TransactionScope,对吧?
  • 是的,你是对的。在 TS 范围内发生的所有事情都必须通过调用 Complete() 显式提交。如果不调用此方法,则不会提交任何内容。所以这也适用于SaveChanges 电话。
猜你喜欢
  • 2011-10-24
  • 1970-01-01
  • 2012-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-17
  • 2018-05-08
  • 1970-01-01
相关资源
最近更新 更多