有什么方法可以模拟结构以在单元测试中获得类?

问题描述 投票:2回答:1

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

模拟不适用于值类型。有没有一种方法可以有效地“模拟结构”?

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;
    }
}
c# tdd moq legacy-code
1个回答
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());
    }
}

我不会声称这两个选项中的任何一个都是漂亮的,在将这种后门放入共享库之前,我会认真考虑安全性。如果可行,通常最好使用接口。

但是,这种事情行之有效,我认为当您遇到不想进行测试的,不友好地重构的代码时,这可能是一个合理的折衷。

© www.soinside.com 2019 - 2024. All rights reserved.