4

这是我第一次涉足实体框架,我有一个使用 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] 部分期间以某种方式切换到测试连接字符串。

4

1 回答 1

3

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

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

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

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

于 2013-09-09T22:22:07.553 回答