【问题标题】:How could you unit test against this bad Linq-To-SQL predicate?您如何针对这个糟糕的 Linq-To-SQL 谓词进行单元测试?
【发布时间】:2015-03-25 19:59:06
【问题描述】:

给出以下示例代码:

Table<Person> tableToQuery = MethodThatGetsReferenceToPersonTable();
string searchType = "NonUser";
IQueryable<Person> queryResults = tableToQuery.Where(p =>
    (p.IsMale == false) && // exclude male people
    type.Equals("User", StringComparison.OrdinalIgnoreCase)
        ? p.User != null : p.User == null);

我是 L2S 新手,但对 EntityFramework 有一些经验。我不希望上述查询在像 EF 这样的 ORM 中正常工作,因为在谓词的三元表达式中调用了 type.Equals。相反,我希望 EF 抛出异常,因为它无法将谓词的那部分转换为 (SQL) 存储表达式。

使用 L2S,.Where 似乎正在返回数据,但不排除 p.Male == truetype == "NonUser 的项目。我已经修复了上面的代码以将type.Equals 三元组从谓词中提取出来并返回正确的结果,现在我正在尝试编写一个测试来断言正确的结果。我遇到的问题是 L2S 代码实际上位于 IRepository&lt;Person&gt; 接口后面。

所以实际代码看起来更像这样:

string searchType = "NonUser";
ICriteria<Person> query = new Query<Person>(p =>
  (p.IsMale == false) && // exclude male people
  type.Equals("User", StringComparison.OrdinalIgnoreCase)
      ? p.User != null : p.User == null);
IQueryable<Person> = _peopleRepository.FindByQuery(query)

...而_peopleRepository 实现只是将query.Predicate 作为参数传递给L2S Table&lt;Person&gt;.Where

问题:

  1. 由于 lambda 谓词中的三元表达式,L2S Table&lt;Person&gt;.Where 未返回正确结果是否正确?我假设是这样,因为根据searchType 的值将三元组取出生成单独的ICriteria&lt;Person&gt; 对象会产生正确的结果。但是我不太确定这是因为 L2S 无法将三元转换为存储表达式,还是由其他原因引起的。

  2. 由于被测方法依赖于IRepository&lt;Person&gt; 实例,我如何才能真正围绕它编写单元测试?在单元测试中模拟 IRepository&lt;Person&gt; 不允许我们测试 lambda 对真实基础数据的影响。创建由某种IList&lt;Person&gt; 支持的FakePersonRepository 也不会揭示实际缺陷,因为上面带有三元表达式的 lambda 使用 linq-to-objects 返回预期结果。有什么方法可以模拟 L2S 的一部分,以便我们可以使用 lambda 生成 SQL,并针对它编写断言?

  3. 这只是我们必须与集成测试和带有连接字符串的实际 L2S 上下文相关的事情,并且无法正确进行单元测试吗?我对“集成测试”的定义是指“使用某种连接字符串连接到在与测试运行程序不同的进程中运行的实际数据库”,而“单元测试”是指“在测试运行程序进程中执行所有操作”。

【问题讨论】:

  • 这也是我从不测试或模拟“原始”LINQ,而只测试或模拟返回结果的组件的原因之一 - 组件本身在 real 数据库的上下文中进行测试(并且仅在它们依赖于正在测试的其他组件时才被模拟)。
  • @user2864740 那么你对问题 #3 说“是”吗?
  • 我相信围绕 L2E/L2S 查询的单元测试从根本上涉及数据库作为“单元”的一部分(并且该单元包含在公开使用权)。在处理事务和横切上下文等时,事情变得更加不确定 - 而这些肯定会推动“单元”信封。但是单元测试不能模拟它正在尝试测试的内容 .. 否则它最终什么都不测试。
  • 也就是说,我相信 L2E/L2S 查询是单元测试,基本上是单元测试原始数据库访问本身,隐藏在强类型查询语法之后。跨度>
  • @user2864740 我明白你在说什么,但是当测试需要连接到一个实际的数据库时——无论是 SQL、LocalDb 还是其他的——它从单元测试到我定义的集成测试。使用我的定义来区分unitintegration,您是否对问题#3 回答“是”?

标签: unit-testing linq-to-sql lambda predicate


【解决方案1】:
  1. L2S (...) 未返回正确结果是否正确

它确实返回了正确的结果,但不是您期望的结果,因为您是作为人类而不是编译器来阅读查询的。后者是这样写的:

tableToQuery.Where(p =>
    ((p.IsMale == false) && type.Equals("User", StringComparison.OrdinalIgnoreCase))
        ? p.User != null
        : p.User == null);

我们人类倾向于阅读:

tableToQuery.Where(p => (p.IsMale == false) && 
     (type.Equals("User", StringComparison.OrdinalIgnoreCase)
        ? p.User != null
        : p.User == null));

2./3.我怎么能围绕这个写一个单元测试呢?

模拟和单元测试几乎是不可能的。当我想测试数据层的行为时,我坚持使用集成测试。对于实体框架,我收集了一些集成测试的原因here。其中大部分也适用于 LINQ-to-SQL。

【讨论】:

  • 关于问题#1,我实际上在问题中犯了一个错误。之前的(错误的)代码实际上是... (p.IsMale == false) &amp;&amp; ... 而不是... p.IsMale == false &amp;&amp; ...。我已经更新了这个问题。这会改变您对问题 #1 的回答吗?
  • 不,没有区别。
  • 嗯,错误代码读取p.IsMale == (false &amp;&amp; ...) 确实有所不同,但编译器对谓词的解释与人眼所见的不同仍然是事实。如果您像我的第二个示例一样添加括号,您会得到正确的(如预期的)结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-09
  • 1970-01-01
  • 2022-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多