首页 > 解决方案 > 有哪些方法可以模拟结构以在单元测试中获取类?

问题描述

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

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

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#tddmoqlegacy-code

解决方案


通过接口引用结构有效地将其转换为引用类型,通过一个称为“装箱”的过程。对结构进行装箱可能会导致行为发生一些细微的变化,您应该在做出决定之前阅读这些内容。

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

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

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

不过,这种事情是可行的,而且我认为当您面对不想侵入性重构的对测试不友好的代码时,这可能是一个公平的妥协。


推荐阅读