如何在单元测试中为被测系统设置调试?

How to setup debugging for the system under test in unit testing?

如何设置调试以便在测试代码下单步调试系统?

我有这个测试:

    [Test]
    public void GetDeviceSettings_is_called()
    {
        //Arrange
        var mockDeviceInteractions = new Mock<IDeviceInteractions>();
        mockDeviceInteractions.SetupSet(p => p._settingsFileQueue = It.IsAny<string>());
        mockDeviceInteractions.Setup(x => x.GetDeviceSettings(It.IsAny<string>(), It.IsAny<string>()));
        //Act
        mockDeviceInteractions.Object._settingsFileQueue = @"C:\";
        mockDeviceInteractions.Object.GetDeviceSettings("123123", "dfgdfg");
        //Assert
        mockDeviceInteractions.VerifyAll();
    }

这是我正在执行的方法:

public virtual DeviceSettings GetDeviceSettingsForSerialNumber(string serial) {

 //do stuff - and i've put a break point here, but it does not step at the break point
GetDeviceSettings(string1, string2);
//do more stuff
    }

我做错了什么?为什么到断点还不停?

您必须模拟 将被测试的 class 调用 的对象,而不是 class本身。

这是一个例子。它假定您可以将依赖项注入 classes.

假设您正在编写一个核控制系统。您想通过编写单元测试来检查系统是否会在需要时指示反应堆执行紧急关闭。

public enum ReactorStatus { Ok, OhDear};

public interface IReactorInteractions {
    ReactorStatus ShutDown(bool nicely);
    ReactorStatus Status { get; }
}

public interface ICore {
    ReactorStatus ShutDown(bool nicely);
    ReactorStatus GetStatus();
}

public class ReactorInteractions : IReactorInteractions {

    private ICore reactor;
    public ReactorInteractions(ICore reactor) {
         this.reactor = reactor;
    }

    public ReactorStatus ShutDown(bool nicely) {
          return reactor.ShutDown(nicely);
    }

    public ReactorStatus Status { 
     get { return reactor.GetStatus(); } 
    }
}

那么,您想测试 ReactorInteractions class。

然后,为了做到这一点,您模拟它调用的对象,在本例中为 ICore您不想在实际的核心上执行操作。这样做至少会违反纪律!

您应该将 ICore mock 的 Object 属性 作为构造函数参数传递给 ReactorInteractions class - 这个 属性 不是你应该在你的测试中访问,它被设计成只传递给被测试的 class - 被测试的 class 作用于这个 'Object',允许你使用 SetupVerify:

private Mock<ICore> mockCore;
private IReactorInteractions reactor;

[SetUp]
public void TestSetup() {
    mockCore = new Mock<ICore>();
    reactor = new ReactorInteractions(mockCore.Object);
}

所以一些示例测试(如果它们是真实的测试,将验证和检查有价值的东西 - 测试应该检查 逻辑,而不是管道。):

[Test]
public void ShutDown_Nicely_Should_Pass()  {
    mockCore.Setup(m => m.ShutDown(true).Returns(ReactorStatus.Ok));
    var status = reactor.ShutDown(true);
    status.Should().Be(ReactorStatus.Ok);
    mockCore.VerifyAll();
}

[Test]
public void ShutDown_Badly_Should_Fail()  {
    mockCore.Setup(m => m.ShutDown(false).Returns(ReactorStatus.OhDear));
    var status = reactor.ShutDown(false);
    status.Should().Be(ReactorStatus.OhDear);
    mockCore.VerifyAll();
}

请注意,我没有在我的测试设置中使用 It.IsAny<bool>。这种语法对于刚接触模拟的开发人员来说非常混乱(在野外看到:尝试使用 It.IsAny 作为测试调用中的参数)——这是我希望 Moq 作者在文档中强调的内容。 It.IsAny 仅当您完全无法控制参数时才应使用。

在你的情况下,没有必要使用 It.IsAny - 你确切地知道你要传递什么值:

const string serial = "123456";
mockDeviceInteractions.Setup(m => m.GetDeviceSettingsForSerialNumber(string serial))
                      .Returns(new DeviceSettings { Serial = serial };

var settings = classUnderTest.GetDeviceSettingsForSerialNumber(serial);
settings.Serial.Should.Be(serial);

然后在测试中,您正在检查是否使用了实际值。如果您测试 It.IsAny 那么请记住,您的代码中处理该值的部分可以替换为随机数生成器并且单元测试仍然会通过。

话虽如此,Moq 有一个限制,即如果列表中的一个参数未知并且必须使用 It.IsAny 那么它们都必须是(我不再使用它所以我不知道情况是否仍然如此,但我似乎记得你可以通过使用回调手动验证参数来解决这个问题)

要回答问题,

How do you set up debugging so that you can step through your system under test code?

与 运行 从 Visual Studio 中的 Start 按钮调试系统没有任何不同。

单元测试实际上只是强调 classpublic API 的代码。单元测试和对 public API 的实际调用之间的唯一区别是你有一些 attributes 要添加到方法中并且你 mock 依赖关系class。 class 本身与正常调试时一样,但您已为其设置场景以处理不同的逻辑,然后您可以 verify 正确发生。

要让你的测试 运行 达到 运行 debug 中的测试,你必须 select 达到 debug 中的 运行 =] 否则大多数 运行 用户默认 运行ning 基本上是 release 模式。根据您使用的 运行ner 的不同,它的实现方式有所不同,但通常允许您 right-click 测试到 debug 和 select 类似 Debug the selected Tests.

解决您的下一个问题,

What am I doing wrong? Why does it not stop at the breakpoint?

当您 运行 如上所述进行单元测试时,您正在测试实际的 class 但正在设置实施的逻辑必须正确处理的场景。

主要问题是您看起来不像是在调用您期望的方法 运行,测试从不调用 GetDeviceSettingsForSerialNumber(...) 除非它是通过以下设置调用的您没有描述的 属性。

您实际上所做的是模拟被测系统,因此实际上并没有测试已实现的代码,但基本上是测试 moq 是否正常工作。这可以通过这一行来识别:

mockDeviceInteractions.Setup(x => x.GetDeviceSettings(It.IsAny<string>(), It.IsAny<string>()));

您正在模拟对 GetDeviceSettings(string, string) 的调用,我不确定此方法的实现,但如果该方法标记为 virtual moq 将在后台创建一个新的覆盖方法,当 any string 作为参数提供时将被调用。

您正在呼叫:

mockDeviceInteractions.Object.GetDeviceSettings("123123", "dfgdfg");

moq 在后台接收此调用,拦截器将此与设置调用之一匹配(见上文),然后它将调用既不执行任何操作也不执行任何操作的设置调用 return 随便什么。

如果在设置 属性(我希望这不是您正在调用的字段)时调用您尝试测试的方法 'GetDeviceSettingsForSerialNumber',则在您设置 属性 调用:

mockDeviceInteractions.SetupSet(p => p._settingsFileQueue = It.IsAny<string>());

我从来没有尝试过,但我不相信它会 运行 实际 属性 setter 在 class 中,这可能就是你的意思正在寻找。

如果您想将 属性 设置为默认值,您可以说:

mock.SetupProperty(f => f.Name, "foo");

现在我可以解释为什么我觉得您正在测试的内容不正确,但我觉得@stuartd 的回答涵盖了 class 应该如何构建具有您可以模拟到 [=106= 的依赖项的结构] 数据而不是在被测系统中模拟调用。相反,我将向您展示如何使用您刚才的结构来构建您的实际测试。

[Test]
public void GetDeviceSettings_is_called()
{
    //Arrange
    var mockDeviceInteractions = new Mock<IDeviceInteractions>();
    var deviceSettings = new Mock<IDeviceSettings>();
    mockDeviceInteractions.Setup(x => x.GetDeviceSettings(@"123123", "dfgdfg"))
        .Returns(deviceSettings.Object)
        .Verifiable();

    //Act
    var actual = mockDeviceInteractions.Object.GetDeviceSettingsForSerialNumber(@"123123");

    //Assert
    Assert.Equal(deviceSettings.Object, actual);
    mockDeviceInteractions.Verify();
}

首先我把你的SetupSet去掉了属性,这个不是必须的,如果你想设置实际的属性只需要在对象上设置即可,我也是删除了正在设置的 属性 ,因为我看不出这如何适合您的测试,除非您期望从您没有描述的 属性 setter 中调用该方法.

接下来,我在 GetDeviceSettingsSetup 中添加了一个 Returns 方法调用,我不确定这个 return 是什么,但你可以在我的示例中看到它 returns a IDeviceSettings 我也将其标记为 Verifiable.

然后我从你的对象中调用实际的 GetDeviceSettingsForSerialNumber。你根本没有调用过它,所以我对你不能命中断点并不感到惊讶。

最后我断言调用的 IDeviceSettings returned 与 GetDeviceSettings 方法 returned 的相同。我知道这与你的实现略有不同,因为你 return 一个具体的 DeviceSettings 但这实际上应该 return 一个 interface.

最后,我正在验证 mockDeviceInteractions,正如您在 Verify 调用中看到的那样,而不是 VerifyAll 调用,因为我偶尔会使用不需要的调用设置模拟想要 Verify 并且只用正确的方法调用标记我确实想要 Verify 的那些。