Tim*_*ter 28 entity-framework iqueryable mocking
我目前正在为MVC4应用程序中的存储库实现编写单元测试.为了模拟数据上下文,我开始采用这篇文章中的一些想法,但我现在发现了一些限制,让我怀疑是否有可能正确模拟IQueryable.
特别是,我已经看到了一些测试通过但代码在生产中失败的情况,并且我无法找到任何方法来模拟导致此失败的行为.
例如,以下代码段用于选择Post属于预定义类别列表的实体:
var posts = repository.GetEntities<Post>(); // Returns IQueryable<Post>
var categories = GetCategoriesInGroup("Post"); // Returns a fixed list of type Category
var filtered = posts.Where(p => categories.Any(c => c.Name == p.Category)).ToList();
Run Code Online (Sandbox Code Playgroud)
在我的测试环境中,我试图嘲弄posts使用假DbSet上面提到的实施,同时也通过创建List的Post实例并将其转换为IQueryable使用AsQueryable()扩展方法.这两种方法都在测试条件下工作,但代码实际上在生产中失败,但有以下例外:
System.NotSupportedException : Unable to create a constant value of type 'Category'. Only primitive types or enumeration types are supported in this context.
虽然像这样的LINQ问题很容易解决,但真正的挑战是找到它们,因为它们不会在测试环境中显示出来.
我期望我可以嘲笑实体框架的实施行为,这是不现实的IQueryable吗?
谢谢你的想法,
蒂姆.
Ger*_*old 63
我认为模拟实体框架行为是非常困难的,如果不可能的话.首先是因为它需要对所有特性和边缘情况有深刻的了解,其中linq-to-entites与linq-to-objects不同.正如你所说:真正的挑战是找到它们.让我指出三个主要领域,而不是声称几乎是详尽无遗的:
Linq-to-Objects成功并且Linq-to-Entities失败的情况:
.Select(x => x.Property1.ToString().LINQ to Entities无法识别方法'System.String ToString()'方法......这几乎适用于本机.Net类中的所有方法,当然也适用于拥有方法.只有少数.Net方法将被翻译成SQL.请参阅CLR方法到规范函数映射.从EF 6.1开始,ToString支持方式.但只有无参数过载.Skip()没有先前OrderBy.Except和Intersect:可以产生可疑的查询抛出SQL语句的某些部分嵌套太深.重写查询或将其分解为较小的查询.Select(x => x.Date1 - x.Date2):DbArithmeticExpression参数必须具有数字公共类型..Where(p => p.Category == category):在此上下文中仅支持原始类型或枚举类型.Nodes.Where(n => n.ParentNodes.First().Id == 1):方法"First"只能用作最终查询操作.context.Nodes.Last():LINQ to Entities无法识别方法'... Last ...'.这适用于许多其他IQueryable扩展方法.请参阅支持和不支持的LINQ方法..Select(x => new A { Property1 = (x.BoolProperty ? new B { BProp1 = x.Prop1, BProp2 = x.Prop2 } : new B { BProp1 = x.Prop1 }) }):类型"B"出现在两个结构不兼容初始化一个LINQ内实体查询......从这里.context.Entities.Cast<IEntity>():无法将类型'Entity'强制转换为'IEntity'.LINQ to Entities仅支持转换EDM原语或枚举类型..Select(p => p.Category?.Name).在表达式中使用空传播抛出CS8072表达式树lambda可能不包含空传播运算符. 这可能会在一天内修复.Linq-to-Objects失败并且Linq-to-Entities成功的案例:
.Select(p => p.Category.Name):when p.Category为null时,L2E返回null,但L2O抛出的Object引用未设置为对象的实例.这不能通过使用空传播来修复(参见上文).Nodes.Max(n => n.ParentId.Value)带有一些空值n.ParentId.L2E返回一个最大值,L2O抛出Nullable对象必须有一个值.EntityFunctions(DbFunctions截至EF 6)或SqlFunctions.成功/失败但行为不同的情况:
Nodes.Include("ParentNodes"):L2O没有包含的实现.它将运行并返回节点(如果Nodes是IQueryable),但没有父节点.Nodes.Select(n => n.ParentNodes.Max(p => p.Id))一些空集合ParentNodes:都失败但有不同的例外.Nodes.Where(n => n.Name.Contains("par")):L2O区分大小写,L2E取决于数据库排序规则(通常不区分大小写).node.ParentNode = parentNode:使用双向关系,在L2E中,这也将节点添加到父节点的集合(关系修正).不在L2O中.(参见单元测试双向EF关系)..Select(p => p.Category == null ? string.Empty : p.Category.Name)::结果相同,但生成的SQL查询也包含空检查,可能更难以优化.Nodes.AsNoTracking().Select(n => n.ParentNode.这个非常棘手!.使用 AsNoTracking EF ParentNode为每个创建新对象Node,因此可能存在重复项.没有 AsNoTracking EF重用现有的ParentNodes,因为现在涉及实体状态管理器和实体密钥.AsNoTracking()可以在L2O中调用,但它不会做任何事情,所以无论是否有它都不会有任何区别.那么模拟懒惰/急切加载以及上下文生命周期对延迟加载异常的影响呢?或者一些查询构造对性能的影响(比如触发N + 1 SQL查询的构造).或由于重复或丢失实体密钥而导致的异常?还是关系修复?
我的意见是:没有人打算这样做.最令人担忧的领域是L2O成功而L2E失败.现在绿色单元测试的价值是多少?之前已经说过EF只能在集成测试中可靠地进行测试(例如这里),我倾向于同意.
但是,这并不意味着我们应该忘记使用EF作为数据层的项目中的单元测试.有办法做到这一点,但我认为,并非没有集成测试.
| 归档时间: |
|
| 查看次数: |
5834 次 |
| 最近记录: |