好的,所以我有这段代码:
public TbMtUserDTO RecoverUser(long userId, UpdateTbMtUserDTO updatedData)
{
TbMtUser user = _usersRepository.FindUserById(userId);
if (user == null ||
(updatedData.IdRecSet == "Password" && String.IsNullOrEmpty(updatedData.DsUpdatedPassword)))
{
return null;
}
switch (updatedData.IdRecSet)
{
case "Username":
return _mapper.Map<TbMtUserDTO>(user);
case "Password":
user.DsPassword = PasswordHasher.Hash(updatedData.DsUpdatedPassword);
_usersRepository.SaveChanges();
return _mapper.Map<TbMtUserDTO>(user);
}
throw new InvalidOperationException(
String.Format(RECOVER_USER_ERROR, updatedData.IdRecSet));
}
当我为那段代码编写测试用例时,当我不得不为其中一种“密码”用例编写测试时,这就是我所做的:
[Fact]
public void UpdatesPasswordSuccessfully()
{
string oldPassword = _user.DsPassword;
UpdateTbMtUserDTO updateTbMtUserDto = new UpdateTbMtUserDTO()
{
IdRecSet = "Password",
DsUpdatedPassword = "new_password"
};
_usersRepositoryMock
.Setup(x => x.FindUserById(It.IsAny<long>()))
.Returns(_user);
_mapperMock
.Setup(x => x.Map<TbMtUserDTO>(It.IsAny<TbMtUser>()))
.Returns(new TbMtUserDTO());
TbMtUserDTO userDto = _usersService.RecoverUser(_user.CdUser, updateTbMtUserDto);
_usersRepositoryMock.Verify(x => x.SaveChanges(), Times.Once);
Assert.NotNull(userDto);
Assert.True(oldPassword != _user.DsPassword);
}
如您所见,该测试的底部有三个断言。我首先检查是否调用了SaveChanges
,然后验证该方法实际上返回了什么,因此返回了NotNull
断言,并且它实际上修改了密码(True
断言)。>
但是我觉得这不是正确的方法。但是在我看来,这些测试是相关的,但是我不确定是否应该将它们分为三个不同的测试。关键是我必须为这三种情况安排相同的零件,老实说,我也不认为这是个好主意。
你们怎么看?我已经进行了几个月的单元测试,所以在这种情况下您的经验法则是什么?
答案 0 :(得分:1)
也许如果您考虑将一个测试拆分为多个测试,则应该将您的方法拆分为多个类/方法并为它们编写测试?我不想深入研究体系结构,但这可以是一个解决方案。特别是我会分开:
if (user == null || (updatedData.IdRecSet == "Password"
&& String.IsNullOrEmpty(updatedData.DsUpdatedPassword)))
{
return null;
}
还有这个
user.DsPassword = PasswordHasher.Hash(updatedData.DsUpdatedPassword);
_usersRepository.SaveChanges();
答案 1 :(得分:0)
有一个规则,即每个测试应测试一个特定方面。但是,这就留下了一个问题,是什么构成一个方面?对我来说,一个经验法则是,如果SUT中发生可能仅影响两个断言之一的可能变化,则两个断言代表两个不同的方面。
举个例子:假设在某些游戏中,您总是从具有定义的3D坐标的特定空间位置(可能是空间站)开始。要测试初始化功能,请检查该初始坐标是否具有期望值。这三个值共同构成一个方面:如果您在某个时间点决定游戏应该在不同的地方开始,则所有三个坐标都将立即更改(嗯,理论上并不需要全部更改,但这很奇怪。巧合)。
在您的示例中,以下事实使情况变得更加复杂:您的函数执行了多项操作,并且将null
的返回用于不同的目的。更具体地说,根据参数的内容,该函数只是执行查找(用户名),或另外进行一些更改(密码)。因此,这不仅是拆分测试的问题,而且可能还在于拆分功能。
我可以想象将其分为两部分:一个执行查询的功能:
TbMtUser user = _usersRepository.FindUserById(userId);
if (user != null) {
return _mapper.Map<TbMtUserDTO>(user);
} else {
return null;
}
第二个更改已查找用户的密码-在您的情况下,这可能并不简单,因为内部使用的类型为TbMtUser
,而返回的类型为TbMtUserDTO
,并且我不清楚它们之间的关系...