EntityFunctions.TruncateTime和单元测试

Jak*_*cki 29 unit-testing entity-framework stub entity-framework-4

我正在使用System.Data.Objects.EntityFunctions.TruncateTime方法在我的查询中获取日期时间的日期部分:

if (searchOptions.Date.HasValue)
    query = query.Where(c => 
        EntityFunctions.TruncateTime(c.Date) == searchOptions.Date);
Run Code Online (Sandbox Code Playgroud)

这个方法(我相信同样适用于其他EntityFunctions方法)不能在LINQ to Entities之外执行.在单元测试中执行此代码(实际上是对象的LINQ)会导致NotSupportedException抛出:

System.NotSupportedException:此函数只能从LINQ to Entities调用.

DbSets在我的测试中使用存根来存储虚假存储库.

那我应该如何对我的查询进行单元测试?

Sla*_*uma 18

您不能 - 如果单元测试意味着您在内存中使用虚假存储库,那么您正在使用LINQ to Objects.如果您使用LINQ to Objects测试查询,则不会测试您的应用程序,而只测试您的假存储库.

你的例外是危险性较小的情况,因为它表明你有一个红色测试,但实际上可能是一个有效的应用程序.

更危险的是另一种情况:进行绿色测试,但崩溃的应用程序或查询不会返回与测试相同的结果.查询如...

context.MyEntities.Where(e => MyBoolFunction(e)).ToList()
Run Code Online (Sandbox Code Playgroud)

要么

context.MyEntities.Select(e => new MyEntity { Name = e.Name }).ToList()
Run Code Online (Sandbox Code Playgroud)

...将在您的测试中正常工作,但不适用于您的应用程序中的LINQ to Entities.

像...这样的查询

context.MyEntities.Where(e => e.Name == "abc").ToList()
Run Code Online (Sandbox Code Playgroud)

... LINQ to Objects可能会返回与LINQ to Entities不同的结果.

您只能通过构建使用应用程序的LINQ to Entities提供程序和真实数据库的集成测试来测试此问题和查询.

编辑

如果您仍想编写单元测试,我认为您必须伪造查询本身或至少在查询中伪造表达式.我可以想象以下代码中的某些内容可能会起作用:

Where表达式创建一个接口:

public interface IEntityExpressions
{
    Expression<Func<MyEntity, bool>> GetSearchByDateExpression(DateTime date);
    // maybe more expressions which use EntityFunctions or SqlFunctions
}
Run Code Online (Sandbox Code Playgroud)

为您的应用程序创建一个实现...

public class EntityExpressions : IEntityExpressions
{
    public Expression<Func<MyEntity, bool>>
        GetSearchByDateExpression(DateTime date)
    {
       return e => EntityFunctions.TruncateTime(e.Date) == date;
       // Expression for LINQ to Entities, does not work with LINQ to Objects
    }
}
Run Code Online (Sandbox Code Playgroud)

......以及单元测试项目中的第二个实现:

public class FakeEntityExpressions : IEntityExpressions
{
    public Expression<Func<MyEntity, bool>>
        GetSearchByDateExpression(DateTime date)
    {
        return e => e.Date.Date == date;
       // Expression for LINQ to Objects, does not work with LINQ to Entities
    }
}
Run Code Online (Sandbox Code Playgroud)

在您使用查询的类中,创建此接口的私有成员和两个构造函数:

public class MyClass
{
    private readonly IEntityExpressions _entityExpressions;

    public MyClass()
    {
        _entityExpressions = new EntityExpressions(); // "poor man's IOC"
    }

    public MyClass(IEntityExpressions entityExpressions)
    {
        _entityExpressions = entityExpressions;
    }

    // just an example, I don't know how exactly the context of your query is
    public IQueryable<MyEntity> BuildQuery(IQueryable<MyEntity> query,
        SearchOptions searchOptions)
    {
        if (searchOptions.Date.HasValue)
            query = query.Where(_entityExpressions.GetSearchByDateExpression(
                searchOptions.Date));
        return query;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用应用程序中的第一个(默认)构造函数:

var myClass = new MyClass();
var searchOptions = new SearchOptions { Date = DateTime.Now.Date };

var query = myClass.BuildQuery(context.MyEntities, searchOptions);

var result = query.ToList(); // this is LINQ to Entities, queries database
Run Code Online (Sandbox Code Playgroud)

FakeEntityExpressions在单元测试中使用第二个构造函数:

IEntityExpressions entityExpressions = new FakeEntityExpressions();
var myClass = new MyClass(entityExpressions);
var searchOptions = new SearchOptions { Date = DateTime.Now.Date };
var fakeList = new List<MyEntity> { new MyEntity { ... }, ... };

var query = myClass.BuildQuery(fakeList.AsQueryable(), searchOptions);

var result = query.ToList(); // this is LINQ to Objects, queries in memory
Run Code Online (Sandbox Code Playgroud)

如果您正在使用依赖注入容器,则可以通过将相应的实现IEntityExpressions注入构造函数并且不需要默认构造函数来利用它.

我已经测试了上面的示例代码并且它有效.

  • 我确实理解L2O和L2E之间的区别 - 我知道我的测试并没有完全复制SQL服务器的行为,但我仍然可以测试很多服务.我很可能会出现假阳性结果 - 如果发生这种情况我可以微调测试.99%的人工作的好处远远超出了风险. (3认同)

Jea*_*nal 15

您可以定义一个新的静态函数(如果需要,可以将它作为扩展方法):

    [EdmFunction("Edm", "TruncateTime")]
    public static DateTime? TruncateTime(DateTime? date)
    {
        return date.HasValue ? date.Value.Date : (DateTime?)null;
    }
Run Code Online (Sandbox Code Playgroud)

然后,您可以在LINQ to Entities和LINQ to Objects中使用该函数,它将起作用.但是,该方法意味着您必须将调用替换EntityFunctions为对新类的调用.

另一个更好(但更具参与性)的选项是使用表达式访问者,并为内存中的DbSet编写自定义提供程序,以将调用替换为对EntityFunctions内存中实现的调用.