【问题标题】:How do I read these moq setup expressions?如何阅读这些起订量设置表达式?
【发布时间】:2019-04-03 14:39:49
【问题描述】:

替代标题:这些表达是什么意思?

编辑:This post 几乎有所帮助,但只是解释了两个设置函数之间的区别,而不是如何阅读它们。

我正在学习如何使用最小起订量并为我的团队进行基础培训,在经历了this video 和查找other stackoverflow posts(以及其他)之后,我认为我理解它足以使用它,但仅在“这就是它的完成方式”的意义上。我没有理由或能力来解释语法。请帮忙。

为了记录,我理解 C# 模板、表达式、函数、动作和事件的单独使用,但是将它们混合在一起?元是真实的。

考虑以下来自视频演示单元测试之一的代码 sn-p,Should_Mock_Events_Based_On_Action()unit test sourceIRepo source):

var mockRepo = new Mock<IRepo>();
mockRepo.Setup(x => x.AddRecord(null))
    .Raises(m => m.FailedDatabaseRequest += null, this, EventArgs.Empty);
  1. Setup(...) 函数似乎是这样写的:“设置一个接受 IRepo 的函数,然后使用 null 调用 AddRecord(...)。”但是这个设置是在模拟IRepo.AddRecord(...),这不是一个表达式。不知何故,这在模拟中被翻译为“为IRepo 设置一个名为AddRecord(...) 的函数,这样,当它使用null 时,它会以某种方式运行。”但它不是这样读的。 这应该怎么读?不知何故,表达式在某个地方变成了一个实际的函数调用。

  2. Raises(...) 函数真的让我很困惑。它看起来像是在说:“之前设置的函数引发了一个事件,该事件接受 IRepo 并将 null 添加到其事件处理程序中。”而且......不知何故,这使得事件发生? += null 操作没有返回任何内容,我无法弄清楚如何将其理解为该处理程序正在寻找的事件。我发现许多文章和 SO 帖子表明这就是它的完成方式,但没有一篇(我还没有找到)解释为什么这应该怎么读?

【问题讨论】:

  • 当使用 null 参数调用 AddRecord IRepo.AddRecord 方法时,它会使用空事件参数 EventArgs.Empty 引发事件 FailedDatabaseRequest
  • 我看到它确实如此,但我发现语法和它的作用之间几乎没有相关性
  • 我认为也许您应该花一些时间学习匿名函数和 LINQ。来到 Moq 了解这些,我发现语法非常明显和清晰 - 但从您的问题和 cmets 看来,您似乎并不了解将函数作为参数传递是如何工作的。在您明白这一点之前,我几乎无能为力 - 知道如何将函数作为参数传递是使用 Moq 的基础。
  • @MattJones :我认为我了解它们是如何工作的,但我不知道它是如何被调用的。在单元测试的任何部分都没有I调用匿名函数,但它显然被调用somewhere。假设我将一个表达式传递给一个函数。怎么办?在被调用之前它什么都不做。那么起订量如何称呼它以及何时?
  • 我深入研究了一下,发现我的困惑源于不理解 C# 的Expression&lt;&gt;。我的问题不是匿名函数、LINQ 或 lambda,而是不知道 C# 可以将表达式解构为它们的基本部分的表达式树,然后在运行时用其他东西构造等效的功能。凉爽的。不过还是感谢您的 cmets。

标签: c# unit-testing moq


【解决方案1】:

假设我有一个接口ITest,它只有一个方法:

public interface ITest
{
    bool IsEvent(int input);
}

然后我想模拟一下 - 请记住,现在我没有实际的具体类。

        var mock = new Mock<ITest>();

现在我想设置 2 个通话:

        mock.Setup(x => x.IsEven(1)).Returns(false);
        mock.Setup(x => x.IsEven(2)).Returns(true);

这是对模拟对象说的:

如果您的方法 IsEven 以 1 的值被调用,则返回 false。
如果你的方法 IsEven 被调用,值为 2 则返回 true。

您正在为您的模拟对象设置行为。

因此,如果我在代码中执行此操作:

        var for1 = mock.Object.IsEven(1);
        var for2 = mock.Object.IsEven(2);

变量for1 为假,for2 为真,因为我告诉了模拟对象它应该做什么。 Setup 的参数有效地表示“这是我希望你注意的行为,然后我会告诉你该怎么做”。就我而言,我使用 Returns 方法来指定在特定情况下从我的模拟对象实际返回的内容。

在您的具体情况下:

        mockRepo.Setup(x => x.AddRecord(null))
            .Raises(m => m.FailedDatabaseRequest += null, this, EventArgs.Empty);

这是对被模拟对象说的

如果有人调用带有 null 参数的 AddRecord 方法,那么 引发 FailedDatabaseRequest 类型的事件

有关Raises 方法的更多信息,请在此处查看Moq quickstart documentation for events

要更深入地了解使用 Moq 引发事件,有一些有用的信息 here - 特别是它谈到了 += null,这让您有些困惑:

要从模拟对象引发事件,我们使用它的 Raise 方法。这个 接受两个参数。第一个是 lambda 表达式,包括 要引发的事件的空事件订阅者。虽然不是 最优雅的语法,这是让 Moq 了解如何 事件被使用。第二个参数提供事件参数 这将包含在活动中。

【讨论】:

  • 最后一个链接有点帮助,但似乎只是说它不是优雅的语法。我挖掘了起订量 github 存储库。我所知道的是mockRepo.Setup(x =&gt; x.AddRecord(null))WhenPhrase.cs 中调用public ISetup&lt;T&gt; Setup(Expression&lt;Action&lt;T&gt;&gt; expression) 并转发到Mock.cs 中的internal static MethodCall Setup(Mock mock, LambdaExpression expression, Condition condition) 我试图弄清楚var mockRepo = new Mock&lt;IRepo&gt;() 如何在WhenPhrase.cs 中设置模拟对象。我最终放弃了。我还是不明白这是怎么回事。
  • 阅读有关使用匿名函数在 C# 中传递函数和动作的信息 - 直到你理解这一点,然后这对你来说是非常不透明的。
【解决方案2】:

回答自己的问题。

我的困惑源于不了解 C# 的表达式是如何工作的(不是 lambda 表达式,而是特别是 Expression&lt;&gt;)。它们是编译器理论(特别是“表达式树”)的暴露,可用于将 lambda 解构为片段,包括参数、常量、二元运算符等。 Moq 利用了这一点,并使用这些片段来构造自己的具有指定功能 + 支持代码的对象以进行验证。我不知道 C# 可以做到这一点。

MSDN Expression

缓解我困惑的教程系列:C# Expression Trees

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-06
    • 2011-09-12
    相关资源
    最近更新 更多