模拟方法执行真实代码而不是模拟,为啥? [关闭]

Posted

技术标签:

【中文标题】模拟方法执行真实代码而不是模拟,为啥? [关闭]【英文标题】:Mocked method executes the real code instead the mock, why? [closed]模拟方法执行真实代码而不是模拟,为什么? [关闭] 【发布时间】:2019-10-03 07:30:23 【问题描述】:

我正在使用 .CallBase() 来执行 TsExporter 类中 .Export() 方法的模拟背后的真实代码。

另一方面,.RetrieveTranslations() 也在执行真正的代码,而不是像“我告诉它这样做”那样返回模拟值。

代码如下:

[TestFixture]
public class TestClass

    private Mock<ITsExporter> _tsExporter;

    [SetUp]
    public void SetUp()
    
        _tsExporter = new Mock<TsExporter>().As<ITsExporter>();

        //This is calling the real code which is good
        _tsExporter.Setup(x => x.Export(It.IsAny<TsFileModel>(), It.IsAny<string>()))
            .CallBase();
        //but this is calling the real code too, and I was expecting not to
        //call it and return the mock instead...
        _tsExporter.Setup(x => x.RetrieveTranslations(It.IsAny<DataTable>(),
                It.IsAny<string>(), It.IsAny<string>()))
            .Returns(new DataTable());
    

    [Test]
    public void Test()
    
        _tsExporter.Object.Export(new TsFileModel(), "");
    

我错过了什么?

谢谢!

【问题讨论】:

你用错了Mocks。查看this blog 以了解如何为单元测试和模拟设计代码。基本上,您注入外部依赖项(ITsExporter),父类将调用您的模拟对象而不是真实对象。 不幸的是,我认为情况并非如此,如果我错了,请纠正我:我没有任何外部依赖项。只有这个类 TsExporter 实现和接口 ITsExporter。方法 Export() 将从其他地方调用(带有一些参数), RetrieveTranslations() 由 Export() 方法在内部调用,这会创建一个数据库连接,这就是我想模拟这个方法的原因,它将是在我们使用数据库的集成测试中进行了测试。我希望这是有道理的。我不想模拟数据库连接,还是应该模拟? 你正在创建一个数据库连接,所以你有一个外部依赖。为了模拟该外部依赖项,您应该以可以模拟该外部依赖项的方式对代码进行建模,即模拟数据库连接(或用于访问数据库的抽象)。 另外,查看您的代码,Export 的设置是毫无意义的,因为它所做的一切CallBase()。您不妨删除它,因为_tsExporter.Object.Export(...) 无论如何都会调用真实方法。嘲笑它,你一无所获。而且,当您移动它时,您可能会看到为什么您的模拟 RetrieveTranslations 没有被调用?提示:您只是在调用不了解您的模拟的真正类:-) 感谢您的回答!如果我将其删除,它将不再调用真实代码,它将模拟所有内容。 【参考方案1】:

您不应该模拟您正在测试的类,而应该模拟该类的依赖关系,以更改它们产生的输出以及它们如何影响您正在运行的代码。单元测试应该只测试一个逻辑位的代码。

看看这个关于如何模拟的简单教程:https://developerhandbook.com/unit-testing/writing-unit-tests-with-nunit-and-moq/

更新

我认为您不应该模拟数据库连接,或者将访问数据库内容的类抽象出到存储库并模拟它。

另一个问题是您使用的是哪种 Db 访问权限?英孚?小巧玲珑?有一些方法可以模拟这些并测试你的类如何处理来自这些的各种返回。

例如英孚:

How to mock EntityFramework Database created by codefirst strategy? 和 How are people unit testing with Entity Framework 6, should you bother?

更新

您认为它是为了调用真正的实现或基类实现是正确的。 但是,除非您尝试编写集成测试,否则它不适用于您的情况。如果您正在编写单元测试,您应该独立于其他类的实际输出来测试单个工作单元。我认为什么是 CallBase 功能是一个不同的问题。据我了解,这意味着调用您继承的类来测试该功能,但您仍然不应该通过模拟实现它的类来测试它,如果它是抽象类,您不应该通过模拟来测试它测试类,否则你正在测试一个不同的类。要么更改类,要么不对其进行测试,要么重新编写原始类以调用该类并查看它的输出。最重要的是,据我了解,您正在调用基础来模拟连接字符串或类似的东西,这对正在运行的代码没有影响,或者至少它不应该因为您正在模拟输出,否则这会已经在进行集成测试了。

值得阅读的好文章是:http://www.codenutz.com/unit-testing-mocking-base-class-methods-with-moq/

【讨论】:

很好的答案!但是,如果我们不应该模拟我们正在测试的类,为什么 moq 具有 CallBase 功能,那么它有什么好处呢?我们应该什么时候使用它?关于数据库访问,我们正在使用 NHibernate。 @AdrianChiritescu 已更新。 我认为 moq 继承了模拟类,基类将是我们的真实类,因此调用真实代码的 CallBase 但我不确定,我会做一些研究然后回来! 如果我将 RetrieveTranslations() 方法设为虚拟,则模拟工作正常,并且将为整个类执行实际代码,但具有模拟返回的 RetrieveTranslations() 方法除外。所以我所缺少的是该方法必须是虚拟的才能完成此行为。 我发现 CallBase() 通常是用来测试抽象类@vsarunov【参考方案2】:

嘲讽很好。

我缺少的是在被测类中使 RetrieveTranslations() virtual

因此,以这种方式,除了 RetrieveTranslations() 方法之外,真正的代码将被执行,该方法将返回模拟告诉它返回的任何内容。

这不是一个好方法,正如人们在 cmets 中提到的那样,我们不应该模拟被测类,但如果有人出于某种原因需要这个,这就是你可以做到的方法。

PS:我重构了这个类,这样我就不必模拟这个类,而是模拟它的依赖项,并在需要的地方注入它们。

【讨论】:

以上是关于模拟方法执行真实代码而不是模拟,为啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

为啥应用内购买方法可以在模拟器上完美运行,但不能在真实设备 iOS 6 上运行?

Mockery 的 andReturn() 执行方法,而不是模拟返回值

如何在真实设备中模拟驾驶路线

为啥模拟位置会跳回真实位置

androidstudio为啥一直在运行一个模拟器

为啥当我遇到真实设备或模拟器时没有显示此 TextViews