在看到一些反对针对模拟测试 EF 的强烈建议之后,尤其是代码优先,我决定针对专用于测试的 SqlCe 数据库进行集成测试,然后在 DbContext 和 DbSet 提供的工作单元和存储库的下游使用纯单元测试。
我只是不清楚在哪里画线以及在哪里测试什么。我知道当我确信 DAL 特定的集成测试覆盖其内部时,我可以在我的服务层中模拟 DAL,但是我在 DAL 中测试什么?测试似乎没有太多意义来查看我是否可以保存和读取对象,因为 EF 是外部的并且已经过测试。
你将使用集成测试在 DAL 中测试映射和查询。例:
public class Service {
private readonly IDAL _dal;
public Service(IDAL dal) {
// Not null validation here
_dal = dal;
}
public void DoSomething() {
SomeData data = FindSomeData();
// Do some logic
_dal.Commit();
}
protected virtual SomeData FindSomeData() {
return _dal.SomeData.Where(...).FirstOrDefault();
}
}
这是一个非常简单的示例,显示:
-
Service
取决于DAL
。DAL
接口通过构造函数注入传递。 Service
包含要测试的公共 DoSomething 方法,以了解逻辑是否正确执行。但这种方法也依赖于数据库查询和数据库持久性(Commit
(。
查询是- 逻辑的一部分,但执行此类查询是单独的问题,因此它由自己的方法处理。在更复杂的情况下,此方法可以在其他类中注入到
Service
类(存储库(中。这些查询方法的关键条件是:- 他们不回来
IQueryable
- 他们不接受
Expression<>
作为参数
- 他们不回来
如何单元测试DoSomething
方法:
- 在这个简单的示例中,测试类将从类派生
Service
并重写FindSomeData
以返回测试数据。在注入的情况下,您将改为为注入类定义 false。 - 您还将模拟
IDAL
,并且可以验证是否调用了Commit
您应该使用什么集成测试:
- 您应该为查询真实数据库
FindSomeData
创建测试 - 一般来说,您还应该对
Commit
进行集成测试,但这更难实现,因为该示例直接从DoSomething
调用了提交。您不想再次测试该方法,同时该方法Commit
泛型情况太多,因为它只是将所有更改从当前上下文刷新到数据库。我通常有单独的测试来插入、更新和删除每个实体类型。当DoSomething
方法进行一些复杂的修改时,您可以将该方法拆分为两个方法,一个由实际逻辑的单元测试处理,另一个由逻辑生成的不同持久性方案的集成测试涵盖。
实体框架已测试,但您的 DAL(尤其是映射(未经过测试。我更喜欢进行集成测试,以向我表明至少我的映射是正确的,更好的是,我可以成功地对我的数据库执行所有 CRUD 操作。
我们通常测试数据库方面的是,复杂的对象图是否可以正确插入、更新、删除;基本上测试更复杂的映射。
在我看来,测试是否可以插入具有 3 个原始值属性的对象等等并没有多大意义,因为那样你永远不会看到它的结束。
我们有点喜欢乐观(简单的东西会起作用(,我们测试更复杂的关联,如果我们在映射中遇到错误(例如,应该删除但没有删除的对象(,我们为此编写一个额外的测试。
从业务角度绝对测试所有内容通常是不明智的;你应该先关注高风险/高伤害的东西,然后沿着严重性阶梯向下努力,直到你认为它不再值得。
1(有一组集成测试来测试你的映射
2( 使 DAL 非常轻量级,但具有足够的能力来构造查询,如下所示:
public interface IDb
{
IQueryable<T>Query<T>();
... (save, delete, get-by-id methods)...
}
3( 编写对象,这些对象封装了针对 DAL 构造查询背后的逻辑
public class MuppetSearch
{
public MuppetColor? Color { get; set;}
public string Name{ get; set; }
public IQueryable<Muppet> ConstructQuery(IDb db)
{
var query = db.Query<Muppet>();
if(Color.HasValue)
{
query = query.Where(m=>m.Value == Color.Value);
}
if(!String.IsNullOrEmpty(Name))
{
query = query.Where(m=>m.Name.Contains(Name));
}
return query;
}
}
4(测试这些,模拟你需要的所有数据应该是微不足道的
5( 在您的服务中使用搜索类进行查询构造