【问题标题】:What are ways to mock out a struct to get a class under unit tests?有哪些方法可以模拟结构以在单元测试中获取类?
【发布时间】:2019-07-10 02:43:26
【问题描述】:

我有一门课,我正试图通过单元测试。该类将结构公开为公共属性。该结构还具有一些公共方法(比结构中的方法做得更多)。我无法对结构进行更改(我不拥有该代码,目前风险太大)。

Mock 不适用于值类型。有没有办法有效地“模拟结构”?

public class SystemUnderTest
{
    public FragileStructCantChange myStruct {get; set;}

    public string MethodUnderTest()
    {
        if(myStruct.LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";        
    }
}

public struct FragileStructCantChange
{
    public string LongRunningMethod()
    {
        var resultString = DoStuff(); //calls other subsystems            
        return resultString;
    }
}

【问题讨论】:

  • 你说你不能改变它,但这包括实现一个接口吗?
  • 可以分享DoStuff的方法吗?
  • @Crowcoder,我也许可以添加一个接口。这就是我倾斜的方式。如果您将其写为答案,我会将其标记为已接受。
  • @Johnny,DoStuff() 真的是一堆电话。在这一点上,我正在努力避免接触 LongRunningMethod()。
  • @BenJoiner 不确定如果您不分享 DoStuff 是否可以回答。实际上这里的重点是模拟DoStuff中的调用...

标签: c# tdd moq legacy-code


【解决方案1】:

通过一个接口引用一个结构体,通过一个称为"boxing" 的过程有效地将其转换为一个引用类型。对结构进行装箱可能会导致一些细微的行为变化,您应该在做出决定之前read about

另一种选择是在结构和被测代码之间添加一些间接性。一种方法是添加一个来指定测试值。

public class SystemUnderTest
{
    public FragileStructCantChange myStruct { get; set; }

    // This value can be overridden for testing purposes
    public string LongRunningMethodOverride { get; set; }

    public string MethodUnderTest()
    {
        // Call the indirect Func instead of referencing the struct directly
        if (LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";
    }

    private string LongRunningMethod()
        => LongRunningMethodOverride ?? myStruct.LongRunningMethod();
}

public class Tests
{

    [Fact]
    public void TestingSideDoor()
    {
        var sut = new SystemUnderTest();

        // Override the func to supply any test data you want
        sut.LongRunningMethodOverride = "successful";

        Assert.Equal("Ok", sut.MethodUnderTest());
    }
}

另一种方法是公开一个可覆盖的函数指针...

public class SystemUnderTest
{
    public FragileStructCantChange myStruct { get; set; }

    // This Func can be overridden for testing purposes
    public Func<string> LongRunningMethod;

    public SystemUnderTest() {
        LongRunningMethod = () => myStruct.LongRunningMethod();
    }

    public string MethodUnderTest()
    {
        // Call the indirect Func instead of referencing the struct directly
        if (LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";
    }
}

public class Tests
{

    [Fact]
    public void TestingSideDoor()
    {
        var sut = new SystemUnderTest();

        // Override the func to supply any test data you want
        sut.LongRunningMethod = () => "successful";

        Assert.Equal("Ok", sut.MethodUnderTest());
    }
}

另一种选择是使用虚拟方法,它可以由子类或模拟框架(在本例中为Moq)伪造...


public class SystemUnderTest
{
    public FragileStructCantChange myStruct { get; set; }

    // This method can be overridden for testing purposes
    public virtual string LongRunningMethod()
        => myStruct.LongRunningMethod();

    public string MethodUnderTest()
    {
        // Call the indirect method instead of referencing the struct directly
        if (LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";
    }
}

public class Tests
{

    [Fact]
    public void TestingSideDoor()
    {
        var sut = new Mock<SystemUnderTest>();

        // Override the method to supply any test data you want
        sut.Setup(m => m.LongRunningMethod())
            .Returns("successful");

        Assert.Equal("Ok", sut.Object.MethodUnderTest());
    }
}

我不会声称这些选项中的任何一个都很漂亮,并且在将这种后门放入共享库之前,我会认真考虑安全性。如果可行,接口通常更可取。

不过,这种方法是可行的,而且我认为当您面对不希望进行侵入式重构的测试不友好代码时,这可能是一个公平的折衷方案。

【讨论】:

  • hm...嘲笑SystemUnderTest很奇怪
  • 是的,这很奇怪——我没有争论。这是一种“不得已”的方式,可以为原本无法测试的东西添加一些可测试性。有时,在重构之前,您只需要脚踏实地获得一些信心。有时,您会遇到无法更改的代码结构。无论哪种方式,我都会接受“奇怪”而不是“根本没有测试”。
  • 我已更新我的答案以包含第三个选项。我并不简单,而且可能不那么两极分化。
  • @xander。关于拳击的好点,我什至没有想到这一点。我也同意模拟 SUT 很奇怪。 '
  • 感谢你们三位提供的一些不错的选择!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-17
  • 1970-01-01
  • 2017-06-29
  • 1970-01-01
  • 2010-11-21
相关资源
最近更新 更多