【发布时间】: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