最小起订量具体 class 不使用虚拟关键字
Moq concrete class without using virtual keyword
情况
我这里有以下代码
ITest.cs
public interface ITest
{
int Prop { get; }
int Calc();
}
Test.cs
public class Test : ITest
{
public int Prop { get; private set; }
public int Calc()
{
return Prop * 2;
}
}
我想测试 Calc()
方法。如果我的理解是正确的,您不能在不使用 virtual
关键字的情况下覆盖具体 类 的 getter 属性。
例如
var moq = new Mock<Test>(); // mocked Test class
moq.Setup(x => x.Prop).Returns(2); // throws an error,
// however with "virtual int Prop { get; }" it doesn't throw any exceptions
var moq = new Mock<ITest>(); // mocked ITest interface
moq.Setup(x => x.Prop).Returns(2); // works fine
// but can not really test the Calc() method (it returns 0
// because the proxy doesn't have Test's implementation)
问题
所以我的问题是:如何在不使 virtual int Prop
的情况下测试 Calc()
的功能
顺便说一句...这也行不通
var moq = new Mock<ITest>();
moq.Setup(x => x.Prop).Returns(2);
var obj = (Test)moq.Object; // can NOT convert ITestProxy to Test
如果你想测试Calc()
,那么测试Calc()
。你不需要模拟任何东西
[TestMethod]
public void Test_Calc_DoublesProp()
{
//arrange
var test = new Test(5); //assumes Prop value can be injected in constructor
//act
int result = test.Calc();
//assert
Assert.IsTrue(result == 10); // 5 * 2
}
虽然感觉不太对,因为它为了测试目的改变了方法实现,只是为了分享:
public class Test : ITest
{
private int _prop;
public int Prop
{
get => ((ITest)this).Prop;
private set => _prop = value;
}
public int Calc()
{
return Prop * 2;
}
int ITest.Prop => _prop;
}
测试中:
var mock = new Mock<Test>();
mock.As<ITest>()
.Setup(x => x.Prop)
.Returns(3);
// This should return 6
mock.Object.Calc();
找到解决方案:Pose
它使用 ILGenerator 并且很旧,但它可以工作。
例子
var test = new Test(); // default value of 'Prop' is 0
var shim = Shim.Replace(() => test.Prop).With((Test @this) => // idk why he needs @this param
{
return 100; // sets the 'Prop' value to 100
});
var result = 0;
PoseContext.Isolate(() =>
{
result = test.Calc(); // prints 200;
}, shim);
模拟将用作替代您要测试的依赖项。
关于您的测试,只需测试 class 的实际暴露行为,这意味着不要尝试使用私有方法和设置器。在任何情况下,模拟 class 进行测试都不是一种有效的方法。
例如:
如果你想测试 Test
class :
[TestClass]
public class TestTests
{
[TestMethod]
public void Calc_WhenPropIsSetTo5_Returns10()
{
/**
* Seems this is what you want to test (see name of methods),
* and I don't think it is a good idea, as your setter is private.
* Since it's not a "public" behavior, you could either
*
* a) have the setter as internal for testing purposes,
*
* b) (my recommendation) go for the two other tests below instead;
* this will actually test the exposed behavior of the class.
*
* c) circumvent the accessibility (by using a library or using
* Reflection directly) to set the value of the prop.
* See your own answer for an example.
*/
}
[TestMethod]
public void Calc_WhenCreatedWith5_Returns10()
{
// Arrange
var testSubject = new Test(5); // if the prop is set by constructor... otherwise arrange in the proper way.
// Act
var actualResult = testSubject.Calc();
// Assert
Assert.AreEqual(expected: 10, actualResult)
}
[TestMethod]
public void Prop_WhenCreatedWith5_IsSetTo5()
{
// Arrange
var testSubject = new Test(5);
// Act
var actualResult = testSubject.Prop;
// Assert
Assert.AreEqual(expected: 5, actualResult)
}
}
如果你想测试使用ITest.Calc()
、
的东西
那么你应该在你的实际代码中,
1 - 取决于接口 ITest
,不取决于实现 Test
2 - 将依赖项 注入 到正在测试的 class,这样你就可以用 Mock<ITest>.Object()
替换通常的实现 Test
public class ClassThatUsesTest
{
private readonly ITest _test;
public ClassThatUsesTest(ITest test)
{
_test = test;
}
public int SomeFunction()
{
if (_test.Calc() == 10)
{
return 42;
}
else
{
return 0;
}
}
}
[TestClass]
public class ClassThatUsesTestTests
{
[TestMethod]
public void SomeFunction_WhenTestCalcReturns10_Returns42()
{
// Arrange
var testMock = new Mock<ITest>();
testMock.Setup(x => x.Calc()).Returns(10);
var testSubject = new ClassThatUsesTest(testMock.Object);
var expectedResult = 42;
// Act
var actualResult = testSubject.SomeFunction();
//
Assert.AreEqual(expectedResult, actualResult);
}
}
在最后一个示例中,您可以看到模拟帮助我们在不依赖 Test
class 的实际实现细节的情况下隔离测试。即使突然实际 Test
被破坏或改变(例如,您需要传递 6 而不是 5 才能得到 10),测试方法也不在乎。
情况
我这里有以下代码
ITest.cs
public interface ITest
{
int Prop { get; }
int Calc();
}
Test.cs
public class Test : ITest
{
public int Prop { get; private set; }
public int Calc()
{
return Prop * 2;
}
}
我想测试 Calc()
方法。如果我的理解是正确的,您不能在不使用 virtual
关键字的情况下覆盖具体 类 的 getter 属性。
例如
var moq = new Mock<Test>(); // mocked Test class
moq.Setup(x => x.Prop).Returns(2); // throws an error,
// however with "virtual int Prop { get; }" it doesn't throw any exceptions
var moq = new Mock<ITest>(); // mocked ITest interface
moq.Setup(x => x.Prop).Returns(2); // works fine
// but can not really test the Calc() method (it returns 0
// because the proxy doesn't have Test's implementation)
问题
所以我的问题是:如何在不使 virtual int Prop
Calc()
的功能
顺便说一句...这也行不通
var moq = new Mock<ITest>();
moq.Setup(x => x.Prop).Returns(2);
var obj = (Test)moq.Object; // can NOT convert ITestProxy to Test
如果你想测试Calc()
,那么测试Calc()
。你不需要模拟任何东西
[TestMethod]
public void Test_Calc_DoublesProp()
{
//arrange
var test = new Test(5); //assumes Prop value can be injected in constructor
//act
int result = test.Calc();
//assert
Assert.IsTrue(result == 10); // 5 * 2
}
虽然感觉不太对,因为它为了测试目的改变了方法实现,只是为了分享:
public class Test : ITest
{
private int _prop;
public int Prop
{
get => ((ITest)this).Prop;
private set => _prop = value;
}
public int Calc()
{
return Prop * 2;
}
int ITest.Prop => _prop;
}
测试中:
var mock = new Mock<Test>();
mock.As<ITest>()
.Setup(x => x.Prop)
.Returns(3);
// This should return 6
mock.Object.Calc();
找到解决方案:Pose
它使用 ILGenerator 并且很旧,但它可以工作。
例子
var test = new Test(); // default value of 'Prop' is 0
var shim = Shim.Replace(() => test.Prop).With((Test @this) => // idk why he needs @this param
{
return 100; // sets the 'Prop' value to 100
});
var result = 0;
PoseContext.Isolate(() =>
{
result = test.Calc(); // prints 200;
}, shim);
模拟将用作替代您要测试的依赖项。
关于您的测试,只需测试 class 的实际暴露行为,这意味着不要尝试使用私有方法和设置器。在任何情况下,模拟 class 进行测试都不是一种有效的方法。
例如:
如果你想测试 Test
class :
[TestClass]
public class TestTests
{
[TestMethod]
public void Calc_WhenPropIsSetTo5_Returns10()
{
/**
* Seems this is what you want to test (see name of methods),
* and I don't think it is a good idea, as your setter is private.
* Since it's not a "public" behavior, you could either
*
* a) have the setter as internal for testing purposes,
*
* b) (my recommendation) go for the two other tests below instead;
* this will actually test the exposed behavior of the class.
*
* c) circumvent the accessibility (by using a library or using
* Reflection directly) to set the value of the prop.
* See your own answer for an example.
*/
}
[TestMethod]
public void Calc_WhenCreatedWith5_Returns10()
{
// Arrange
var testSubject = new Test(5); // if the prop is set by constructor... otherwise arrange in the proper way.
// Act
var actualResult = testSubject.Calc();
// Assert
Assert.AreEqual(expected: 10, actualResult)
}
[TestMethod]
public void Prop_WhenCreatedWith5_IsSetTo5()
{
// Arrange
var testSubject = new Test(5);
// Act
var actualResult = testSubject.Prop;
// Assert
Assert.AreEqual(expected: 5, actualResult)
}
}
如果你想测试使用ITest.Calc()
、
的东西
那么你应该在你的实际代码中,
1 - 取决于接口 ITest
,不取决于实现 Test
2 - 将依赖项 注入 到正在测试的 class,这样你就可以用 Mock<ITest>.Object()
替换通常的实现 Test
public class ClassThatUsesTest
{
private readonly ITest _test;
public ClassThatUsesTest(ITest test)
{
_test = test;
}
public int SomeFunction()
{
if (_test.Calc() == 10)
{
return 42;
}
else
{
return 0;
}
}
}
[TestClass]
public class ClassThatUsesTestTests
{
[TestMethod]
public void SomeFunction_WhenTestCalcReturns10_Returns42()
{
// Arrange
var testMock = new Mock<ITest>();
testMock.Setup(x => x.Calc()).Returns(10);
var testSubject = new ClassThatUsesTest(testMock.Object);
var expectedResult = 42;
// Act
var actualResult = testSubject.SomeFunction();
//
Assert.AreEqual(expectedResult, actualResult);
}
}
在最后一个示例中,您可以看到模拟帮助我们在不依赖 Test
class 的实际实现细节的情况下隔离测试。即使突然实际 Test
被破坏或改变(例如,您需要传递 6 而不是 5 才能得到 10),测试方法也不在乎。