我有一个要在单元测试中尝试的类。该类将结构公开为公共属性。该结构还具有一些公共方法(比结构中的方法应做的更多)。我无法对结构进行更改(我不拥有该代码,目前风险太大)。
模拟不适用于值类型。有没有一种方法可以有效地“模拟结构”?
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;
}
}
通过称为"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()); } }
我不会声称这两个选项中的任何一个都是漂亮的,在将这种后门放入共享库之前,我会认真考虑安全性。如果可行,通常最好使用接口。
但是,这种事情行之有效,我认为当您遇到不想进行测试的,不友好地重构的代码时,这可能是一个合理的折衷。