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

时间:2016-02-16 17:45:29

标签: c# .net visual-studio unit-testing moq

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

我有这个测试:

var bens = $filter('dateFilter')(ben.GetAllEventsByUserResult.HOLIDAY_START);

这是我正在采取的方法:

    [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();
    }

我做错了什么?为什么它不会在断点处停止?

2 个答案:

答案 0 :(得分:2)

您必须在被测试的类中模拟 将被称为 的对象,而不是类本身。

这是一个例子。它假设您可以将依赖项注入您的类。

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

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类。

为了做到这一点,然后,你模拟它调用的对象,在这种情况下是ICore你不想对实际的核心进行操作。这样做肯定是违纪行为!

您应该将Object mock的ICore属性作为构造函数参数传递给ReactorInteractions类 - 此属性不是您应该在测试中访问的属性,它仅用于传递给正在测试的类 - 被测试的类正是这个'对象',允许您使用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那么它们都必须是(我不再使用它,所以我不知道是否仍然如此,但我似乎记得你可以通过使用回调手动验证参数来解决这个问题。

答案 1 :(得分:1)

要回答这个问题,

  

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

没有必要执行的特定设置与调试从Visual Studio中的Start按钮运行的系统有任何不同。

单元测试实际上只是强调public class API的代码。单元测试和对公共API的实际调用之间的唯一区别是,您有一些attributes要添加到方法中,并且您在类中有mock个依赖项。 类本身保持与正常调试时相同,但是您有设置方案来处理不同的逻辑,然后您可以verify正确地发生。

要让您的测试运行器在debug中运行测试,您必须选择它在debug中运行,否则大多数跑步者默认以基本release模式运行。根据您使用的跑步者而言,这取决于不同的方式,但大多数情况下,您通常会right-click将测试debug Debug the selected Tests并选择GetDeviceSettingsForSerialNumber(...)行。{/ p>

解决您的下一个问题,

  

我做错了什么?为什么它不会停在断点处?

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

主要问题是你看起来好像没有调用你期望运行的方法,测试从不调用moq,除非通过设置你所拥有的属性来调用t描述。

你实际做的是模拟被测系统,因此实际上没有测试实现的代码,但基本上测试mockDeviceInteractions.Setup(x => x.GetDeviceSettings(It.IsAny<string>(), It.IsAny<string>())); 是否正常工作。这可以通过以下方式识别:

GetDeviceSettings(string, string)

您正在嘲笑对virtual的调用,我不确定此方法的实现,但如果方法标记为moq string,则会在后台创建一个新的重写方法,当任何 mockDeviceInteractions.Object.GetDeviceSettings("123123", "dfgdfg"); 作为参数提供时将被调用。

然后你正在打电话:

moq
后台的

mockDeviceInteractions.SetupSet(p => p._settingsFileQueue = It.IsAny<string>()); 正在接收此调用,并且拦截器将其与其中一个设置调用匹配(见上文),然后它将调用setup调用,该调用既不做任何事情也不返回任何内容。

如果您尝试测试'GetDeviceSettingsForSerialNumber'的方法是在属性(我希望这不是您正在调用的字段)被设置的情况下被调用的,那么当您使用调用设置属性时,这将被取消:

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

我从来没有尝试过这个,但我不相信它会在类中运行你可能正在寻找的实际属性设置器。

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

class

现在我可以解释为什么我觉得你测试的是不正确的,但我觉得@stuartd的答案涵盖了[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

首先我删除了你Returns的属性,这不是必需的,如果你想设置实际属性只是在对象上设置它,我也删除了设置的属性,因为我不能看看这是如何适合你的测试的,除非你期望从你没有描述的属性设置器中调用该方法。

接下来我已经向Setup的{​​{1}}添加了GetDeviceSettings方法调用,我不确定这返回了什么,但是你可以看到它返回{{1}在我的示例中,我还将其标记为IDeviceSettings

然后我从该对象调用实际的Verifiable。你从来没有打电话给我,所以我不会惊讶你不能打破一个断点。

最后,我断言调用返回的GetDeviceSettingsForSerialNumberIDeviceSettings方法返回的GetDeviceSettings相同。我知道这与您的实现略有不同,因为您返回具体的DeviceSettings但实际上应该返回interface

最后,我正在通过mockDeviceInteractions来电验证Verify,而不是VerifyAll来电,因为我偶尔会设置一个我不需要调用的模拟Verify并且只使用正确的方法调用标记我想要Verify的那些。