比方说,我有一个简单的类,叫做MyRequestHandler,它有一个叫做ProcessRequest的方法,它只是简单地接受一个请求对象,把它映射到一个返回对象,然后返回这个对象。 这显然是一个非常简单的例子,我正在研究一个更复杂的方法测试)。
public class MyRequestHandler
{
private IMapper _mapper;
public MyRequestHandler(IMapper maper)
{
_mapper = mapper;
}
public MyReturnObject ProcessRequest(MyRequestObject requestObject)
{
MyReturnObject returnObject = _mapper.Map<MyReturnObject>(requestObject);
return returnObject;
}
}
现在在单元测试中(使用Xunit),我想测试ProcessRequest方法,但很明显,我想Moq Map方法,因为这样。
MyRequestObject requestObject = new RequestObject()
{
RequestInt = 1,
RequestString = "Hello"
};
MyReturnObject returnObject = new MyReturnObject()
{
MyInt = 1,
MyString = "Hello"
};
Mock<IMapper> mockMapper = new Mock<IMapper>();
mockMapper.Setup(m => m.Map<MyRequestObject>(requestObject)).Returns(returnObject);
MyRequestHandler requestHandler = new MyRequestHandler(mockMapper.Object);
MyReturnObject response = requestHandler.ProcessRequest(requestObject);
Assert.Equal(returnObject.MyInt, response.MyInt);
Assert.Equal(returnObject.MyString, response.MyString);
这里的问题是Moq会返回(我想这应该是很明显的)一个对returnObject的引用,所以我的断言总是会通过,即使我的方法在返回对象之前改变了一个值。 现在我可以在Moq SetupReturn中实例化一个新的MyReturnObject,然后通过我给新对象的值来比较MyInt和MyString,但是如果它是一个非常复杂的对象,有20个属性和对象列表呢? 也许我想使用AutoFixture来创建被返回的对象,然后使用DeepEqual来比较它们? 这可能吗? 是我看错了,还是我必须在SetupReturn中进行某种类型的克隆才能使之工作?
我不相信有内置的功能来检测被测方法没有改变传递给它的对象。
选项:确保返回对象是不可变的。
如果可能的话,我更喜欢不可变的对象--这样可以防止写错代码的可能性,从而减少所需的测试量。
在这种情况下,你没有收到一个新的数据,可以验证行为。
在这种情况下,内部状态是没有价值的
var requestObject = new RequestObject();
var returnObject = new MyReturnObject();
...
var actual = requestHandler.ProcessRequest(requestObject);
Assert.AreSame(returnObject, actual);
mockMapper.Verify(
instance => instance.Map<MyRequestObject>(requestObject),
Times.Once);
一些细节
我们不能与他人共享写权限,所以我假设你有
public class MyRequestObject{ int RequestInt { get; private set; } string RequestString { get; private set; }}。
否则你总是应该测试参数突变。你可以想象有10个参与者被深度调用,他们每个人都应该有这样的测试。这些测试对变化是很弱的,它们对新属性没有任何作用。
最好是有一个好的编码约定,有时做codereview。例如,有人可以随机删除 私人 的属性,并且它不能被任何测试所捕获。
有很多好的做法,比如 "在代码之前写测试 "等等。