【问题标题】:Using FakeItEasy, is it possible to create a dummy object of a type that takes generic type parameters使用 FakeItEasy,是否可以创建一个采用泛型类型参数的类型的虚拟对象
【发布时间】:2016-05-31 02:03:59
【问题描述】:

我有以下测试:

[Fact]
public void StartProgram_CallsZoneProgramStart()
{
    var zone = A.Fake<Zone>();
    zone.StartProgram();
    A.CallTo(() => zone.ZoneProgram.Start(null, A.Dummy<ActionBlock<InterruptInfo>>())).MustHaveHappened(Repeated.Exactly.Once);
}

它正在创建一个ActionBlock&lt;InterruptInfo&gt; 类型的虚拟对象,它被传递到 MustHaveHappened 调用中。 zone.StartProgram 肯定调用了 zone.ZoneProgram.Start 方法,但是 FakeItEasy 看不到这个调用。它返回以下错误消息:

Assertion failed for the following call:
  ZoneLighting.ZoneProgramNS.ZoneProgram.Start(<NULL>, ActionBlock\`1 Id=1)
Expected to find it exactly once but found it #0 times among the calls:
  1: ZoneLighting.ZoneProgramNS.ZoneProgram.Start(inputStartingValues: Faked ZoneLighting.ZoneProgramNS.InputStartingValues, interruptQueue: ActionBlock`1 Id=2)
  2: ZoneLighting.ZoneProgramNS.ZoneProgram.Start(inputStartingValues: <NULL>, interruptQueue: ActionBlock`1 Id=2)

从错误消息中可以看出,正在比较的 ActionBlock 上的 ID 不同(1 和 2),这就是它无法看到调用的原因。我的问题是,为什么虚拟 ActionBlock 的 ID = 1?我认为作为一个虚拟对象,它不应该有任何具体的细节,如 ID 等。这是因为泛型类型不能被虚拟化吗?

我在这里看到了类似的东西:https://github.com/FakeItEasy/FakeItEasy/issues/402

但我无法弄清楚这是否在谈论同一件事。任何帮助将不胜感激。

【问题讨论】:

    标签: c# .net mocking xunit.net fakeiteasy


    【解决方案1】:

    我对@9​​87654326@s 不熟悉,所以我不确定他们的Id 值是从哪里得到的,但我想我可以对你的测试中发生的事情有所了解。

    首先,我认为您对Dummy 是什么感到困惑。如果是这样,请不要难过。它们可能有点令人困惑。从Dummy documentation,一个 Dummy 是

    Dummy 是 FakeItEasy 在需要某种类型的对象时可以提供的对象,但对象的实际行为并不重要。

    当 FakeItEasy 需要创建一个对象以提供给类构造函数时(我们稍后会看到更多),或者当它需要从方法或属性返回不可伪造的对象时,它们主要由 FakeItEasy 本身使用。最终用户很少需要创建它们。

    Dummy 是一个真实的对象(它必须是——否则,我们怎么能对它做任何事情呢?)。它必须包含其类型所具有的任何具体细节(在本例中为ActionBlock&lt;InterruptInfo&gt;)。对 Dummy 泛型类型没有任何限制。

    查看how a Dummy is made 的文档,我们可以看到,由于ActionBlock&lt;InterruptInfo&gt; 可能没有可用的自定义IDummyDefinition(你有吗?),它不是任务,也不是可伪造的(因为类是密封的),然后通过调用 ActionBlock constructors 之一来创建 Dummy,并让 Dummies 满足每个参数。

    我猜 ActionBlock 有 ID。它们是如何分配的,我不知道,但如果它们是一个好的 ID,那么看起来我们有两个不同的 ActionBlock&lt;InterruptInfo&gt;s:一个在 zone.StartProgram 中提供,另一个是在测试中制作的 Dummy。

    ActionBlocks 文档建议它不会覆盖Equals,因此将执行参考比较,并且两个 ActionBlocks(Dummy 和生产代码中使用的那个)不匹配。这就是 FakeItEasy 无法识别呼叫的原因。

    如果您只是想查看是否使用第一个参数 null 和第二个参数一些 ActionBlock 对 zone.ZoneProgram.Start 进行了调用,我想您可能打算使用:

    A.CallTo(() => zone.ZoneProgram.Start(null, A<ActionBlock<InterruptInfo>>.Ignored))
                .MustHaveHappened(Repeated.Exactly.Once);
    

    Ignored 也可以缩写为 _。如果您愿意,请阅读更多关于 ignoring argument values 的信息。)

    这可能会让您摆脱眼前的问题,尽管我担心两件事:

    1. 看起来zone.ZoneProgram.Start 被调用了两次,而不是“Exactly.Once”,但我相信您将能够处理这个问题,并且
    2. 通常,伪造被测对象被认为是一种反模式。通常,人们会向被测产品代码提供虚假的依赖关系。我并不是说它不起作用,但有时它会导致混乱。不过,在当前问题得到解决后,这可能是另一天的问题。

    希望对你有所帮助。

    哦,您问的是问题 402。该问题是关于在定义将控制如何创建虚拟对象的自定义类时赋予用户更多权力。除非您创建了一个扩展 IDummyDefinitionDummyDefinition 的类,否则它可能在这一点上不相关。

    【讨论】:

    • 感谢您的详细回复。我不知道不能像这样对密封类进行虚拟化。所以我的后续问题是:从测试的角度来看,如何知道是使用假人还是使用 .Ignored 技术?在这两种情况下,类型都是必需的,但对象的实际行为并不重要(引用您回复中的 Dummy 文档)。
    • 关于将被测对象伪装成反模式的观点,为什么会这样?如果我们只是测试对象的“管道”以确保应该连接的部分在方法调用方面是连接的呢?在不伪造对象的情况下如何进行测试?
    • Ignored 在传递给调用的参数无关紧要时使用。一般来说,对于呼叫匹配语义,Argument Constraints 技术是可以使用的。一个 Dummy,一旦被构造,就是一个像任何其他对象一样的对象,因此在 A.CallTo 中提供它意味着它将使用 Equals 与实际参数进行比较。如果您希望比较结果显示“真”,那么您确实关心答案,因此 Dummy 是一个糟糕的选择。即使它碰巧起作用,也不清楚为什么。
    • 关于反模式,StartProgram 调用ZoneProgram.Start 很好,但是我们不知道ZoneProgram 在这里是什么——这是zone 编造的东西,如据我所知。内部部分是连接的这一事实很重要,但只有对象才应该关心这一点——我们通常关心外部可见的行为。检查内部管道大多只会使代码难以重构。当然,这只是一个指导方针。你可能有一个有效的案例来测试这种方式,如果没有全貌,也许我不应该评论。
    • 澄清一下,密封类可以是傻瓜。只是它们根本不是假货。它们只是使用Activator.CreateInstance 实例化的类的实例。
    猜你喜欢
    • 2018-06-12
    • 2020-03-23
    • 2013-06-27
    • 1970-01-01
    • 1970-01-01
    • 2019-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多