【问题标题】:Calling .CreateSourceQuery() on Navigation Property returns null when Unit Testing; works on actual database, how do I setup my test data to match?单元测试时,在导航属性上调用 .CreateSourceQuery() 返回 null;适用于实际数据库,如何设置我的测试数据以匹配?
【发布时间】:2012-01-30 02:52:55
【问题描述】:

我有一个方法将我的实体框架实体转换为 DTO 对象。在这种方法中,我有参数可以跳过并限制要返回的相关项目的数量。对于小数据集,这样的简单查询效果很好:

var query = this.AccessLogs
    .Skip(skipRelated)
    .Take(takeRelated);

对于更大的数据集,我发现这实际上在我的数据库中执行了 SELECT * 并导致了很多问题,因为在某些情况下我有数百万条相关记录。在询问this question 之后,我将查询修改为:

var query = this.AccessLogs
    .CreateSourceQuery()
    .OrderBy(p => p.ID)
    .Skip(skipRelated)
    .Take(takeRelated);

现在,虽然这解决了我在集成测试期间遇到的性能问题,但这会导致我的每一个单元测试都失败,因为 .CreateSourceQuery() 返回 null 然后我的 .OrderBy() barfs 在参数名称上带有 ArgumentNullException:source .

我有一个返回IQueryable<T> 的存储库,并且我有依赖注入设置来对其进行单元测试,所以我正在设置我的“测试”数据,就像这样。最初我只是使用List<T>,但我发现this article 使用InMemoryObjectSet<T> 进行测试。无论哪种方式,我对.CreateSourceQuery() 的调用都会返回 null,即使基础集合中有 数据。

IObjectSet<Parent> ret = new InMemoryObjectSet<Parent>();
var parent = new Parent();
parent.ID = 1;
parent.Name = "Name 1";
for(int i = 0; i < 5; i++)
{
    var ch = new Child();
    ch.ID = i;
    ch.ParentID = 1;
    ch.Property1 = "Name " + i.ToString();
    parent .Children.Add(ch);
}
ret.AddObject(parent);

我的问题是:如何设置单元测试的测试数据,以使 .CreateSourceQuery() 不返回 null?

【问题讨论】:

    标签: c# .net entity-framework unit-testing test-data


    【解决方案1】:

    你不会的。如果您正在测试与 EF 相关的代码,则必须使用真实的 EF 和真实的数据库进行测试 - 没有办法解决这个问题,任何避免它的尝试都等于 not testing your application,但是关于 EF 应该如何工作的一些假设。

    为什么在你的情况下不可能伪造? CreateSourceQueryEntityCollection 的一个方法,实体集合依赖于真实的ObjectContext。同时EntityCollection 被密封。此方法在任何公共接口中也不可用。所以没有办法用普通的单元测试 API 替换它的逻辑。唯一的选择是使用一些更先进的技术,这将允许您将方法调用重定向到其他一些(这仅由商业 TypeMock Isolator 和 MS Moles 提供)但这会导致与您目前尝试做的相同的问题=> 测试关于你不拥有的代码的假设。只有当您的测试没有测试与 EF 相关的代码、查询或持久性时,任何形式的伪装才有意义——这些东西必须由单独的集成测试覆盖。

    如何避免?在您测试的类中创建新方法:

    protected virtual IEnumerable<AcessLog> GetLogs(int skipRelated, int takeRelated)
    {
         return this.AccessLogs.
                    .CreateSourceQuery()
                    .OrderBy(a => p.ID)
                    .Skip(skipRelated)
                    .Take(takeRelated);
    }
    

    现在在您的测试中不要使用您的原始类,而是使用派生类以某种方式覆盖 GetLogs 方法并返回您在测试中所期望的内容。

    但是等等。我刚刚在GetLogs 中跳过了对您的逻辑的测试,不是吗?是的,我确实做到了。如上所述,此代码无法进行单元测试。它必须通过使用真实数据库的单独集成测试来覆盖,但是您将代码隔离在单个方法中,并且依赖于此方法的所有其他逻辑都可以通过伪造此方法进行单元测试。

    它仍然无法涵盖CreateSourceQuery 可能遇到的所有问题。例如,如果您的关系已经加载,会发生什么?或者,如果您的实体因某种原因脱离,会发生什么?这些是不容易测试的副作用。

    【讨论】:

    • 那么,您会说这篇文章 (blogs.msdn.com/b/adonet/archive/2009/12/17/…) 与您链接的 SO 帖子相同吗?例如,不测试我的应用程序?
    • 如果您要测试例如 LINQ 查询,那么这篇文章是错误的(在许多其他部分都是错误的,但这与您的问题无关)。本文针对 EF 的单个部分,您可以在其中伪造某些东西,但所有其他 EF 部分都是刚性的。因此,它与您的问题无关 - CreateSourceQuery 不容易伪造。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-10
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    相关资源
    最近更新 更多