【问题标题】:Is it ok to mock parts of the class I am testing?可以模拟我正在测试的课程的一部分吗?
【发布时间】:2013-10-17 09:25:34
【问题描述】:

我目前正在为一个基于大型 xml 文件中的参数格式化值的类编写单元测试。

我正在测试的类在其构造函数中接收另一个类,该类提供解析和读取 xml 文件的功能。我认为给被测试类一个 xml 读取类的具体实例是不好的风格,因为我相信这样做会导致每次我想测试 xml 读取类 - 事实上 - 测试主类的格式化功能.如果xml读取类出现问题,格式化类中的所有单元测试都会失败,这显然不是格式化类的问题。

那我应该怎么做呢?

显然我会创建一个 xml 读取类的模拟并将其作为参数传递给构造函数。然而格式化类会使用这个实例来创建大约 5 个其他类的私有实例。

因为我不知道这些类想要做什么(老实说,这些测试不应该关心)我想模拟掉我正在测试的类的这些私有字段。

这样可以吗?我将如何使用 Moq 做到这一点?

-编辑-

看下面的例子:

public class FormatterCore : IFormatterInterfaceIWantToTest
{
    public FormatterCore(IConfigService service)
    { 
      this.something = new SomeStuffA(service);
      this.somethingThatINeed = new SomethingUserfull(service);
      this.somethingElse = new SomeOtherStuff(service);
      this.somethingTotallyDifferent = new SomeReallyUselessStuff(service);
      //... 
    }    
    public T Format<T>(object input, string id)
    {
      // implementation of the interface I want to test
    }
}

在我的示例中,我想测试接口的方法Format&lt;T&gt;()。要创建 Formatter 类的实例,我需要传递 IConfigService 实现的实例(这既昂贵又麻烦,因为它需要不同的 xml 文件并且需要一段时间)。我的问题是我不想为每个单元测试创​​建 configService 的实例,因为这意味着我将在 FormatterCore 单元中的每个测试中测试 configService 本身。

【问题讨论】:

  • 但是格式化类会使用这个实例来创建大约 5 个其他类的私有实例。很高兴看到一个例子。
  • @DominicKexel 我明白了,我编辑了我最初的问题

标签: c# unit-testing moq


【解决方案1】:

为了测试FormatterCore,您不应创建IConfigService 实现的实例。您必须创建并设置 IConfigService 的模拟对象。

[TestClass]
public class FormatterCoreTest
{
    Mock<IConfigService> сonfigServiceMock;

    [TestInitialize]
    public void Init()
    {
        сonfigServiceMock = new Mock<IConfigService>();
    }

    [TestMethod]
    public void Format()
    {
        // arrange
        var input = /* input value */;
        var id = /* id value */;
        var сonfigServiceMock
            .Setup(services => services.YourMethodToMock())
            .Returnes(/* expected result or behaviour */);

        // act
        var target = new FormatterCore(сonfigServiceMock.Object);

        var result = target.Format</* AnyType */>(input, id);

        // assert
        /* Your asserts */
        result.Should().Be(/* expectred result */);
        Assert.AreEqual /* expectred result */, result);
    }
}

类型 SomeStuffASomethingUserfullSomeOtherStuffSomeReallyUselessStuff 是嵌套的且无法测试或公开且有可能吗?

如果可以测试类型SomeStuffASomethingUserfullSomeOtherStuffSomeReallyUselessStuff,那么最好将它们的实例注入到 FormatterCore 的构造函数中,而不是在构造函数中创建它们。

public class FormatterCore : IFormatterInterfaceIWantToTest
{
    ISomeStuffA something;
    ISomethingUserfull somethingThatINeed;
    ISomeOtherStuff somethingElse;
    ISomeReallyUselessStuff somethingTotallyDifferent;

    public FormatterCore(
        ISomeStuffA someStuffA,
        ISomethingUserfull somethingUserfull,
        ISomeOtherStuff someOtherStuff,
        ISomeReallyUselessStuff someReallyUselessStuff
        )
    {
        this.something = someStuffA;
        this.somethingThatINeed = somethingUserfull;
        this.somethingElse = someOtherStuff;
        this.somethingTotallyDifferent = someReallyUselessStuff;
        //... 
    }

    public T Format<T>(object input, string id)
    {
        // implementation of the interface I want to test
    }
}

让您的 IoC 负责创建实例。

需要为每个测试中的所有依赖项创建和设置模拟。

【讨论】:

    【解决方案2】:

    由于您无法访问 XML 格式化类的私有变量(除了通过反射侵入该类),并且您无法确定其他类何时创建,我认为您不能以你喜欢的方式嘲笑他们。不得不侵入一个类来访问私有变量或测试方法是一种代码味道——这意味着你隐藏了应该公开的可测试功能。

    因此,要公开该功能,您最好的做法似乎是注入 XML 格式化类用来创建这些其他类的工厂。您的 XML 阅读器/解析器模拟将传递给 Create 方法,并且您将返回这些类的适当模拟以供 XML 格式化类使用。

    或者,您可以像在集成测试中一样对待 XML 格式化类 - 接受将使用您的 XML 读取器/解析器模拟作为参数创建其他类,并设置该模拟以期望来自它们的调用.

    【讨论】:

    • 你能详细说明注入工厂的部分吗?如果我理解正确,这将需要更改测试代码以公开内部字段的创建。恐怕我做不到。
    • 啊,如果你不能改变FormatterCore,那么注入工厂是行不通的。我建议您设置 IConfigService 模拟以期待来自 SomeStuffA 和其他人的电话。将FormatterCore 及其创建的类视为一个单元。您可以使用反射将这些私有字段设置为其他模拟(as outlined here,但找出这些类之间的依赖关系并设置存根调用可能非常棘手,具体取决于它们的交互方式。
    猜你喜欢
    • 1970-01-01
    • 2018-12-27
    • 1970-01-01
    • 1970-01-01
    • 2013-07-01
    • 2021-11-25
    • 1970-01-01
    • 2018-04-19
    相关资源
    最近更新 更多