好的,这可能是一个危险的问题。我已经做了一段时间的单元测试,但由于某种原因,我今天早上醒来,我问自己这个问题。
假设我有一个UserFactory接口,它有CreateUser方法。
在某些时候我需要创建一个用户权限?所以我创建了一个测试,检查是否在适当的位置为UserFactory调用了CreateUser。
现在,单元测试非常适合实际代码 - 这很好。但也许有点太多了?同样,打破测试的唯一方法是不调用CreateUser。我们没有检查它的实现等,但只是检查接口已被调用。但无论谁删除该调用,都会有一个失败的测试,并最终从步骤中删除验证语句以验证CreateUser是否被调用。
我已经看到这种情况一遍又一遍地发生。
有人可以将光线带回给我并解释为什么验证模拟对象的方法被调用是有益的吗?我可以看出为什么设置它们可能有用,比如CreateUser应该为后面的部分代码返回一个虚拟用户,但是在我们简单的地方并且只验证它们是否被调用是得到我的部分。
谢谢!
答案 0 :(得分:2)
您不仅验证了已调用的接口,还可以对接口的不同行为进行多次测试。尤其是极端情况 - 当CreateUser 返回错误引发异常时,您的代码是否优雅地进行故障转移?
答案 1 :(得分:2)
打破测试的唯一方法是不调用CreateUser
因此,在具有一些复杂性,一些条件等的方法中,跳过该调用可能非常容易;以后的维护可能会无意中导致错过呼叫。
所以我认为这些副作用测试可以有价值。在您的情况下,是否应始终调用CreateUser?什么时候抛出异常?在某些情况下,检查CreateUser 不时可能会有值。
我同意你的观点,在简单的情况下,有时候我们的测试和代码会或多或少地重复自己,而维护变成一种无意识的“改变代码,改变测试”活动。我认为当有更多路径和错误处理时,值会变得更清晰。
答案 2 :(得分:2)
验证模拟对象通常是必要的恶魔,正如您所提到的,单元测试有时与被测试的类紧密耦合。
我不知道如何回答你的问题,但我会尝试。
以这段代码为例,其中userRepository是一个依赖项(示例并不完美)。
public void doSomething(User user) {
if( user.isValid() ) {
userRepository.save(user)
} else {
user.invalidate();
}
}
测试此方法的一种方法是插入连接到数据库的真实存储库,并验证用户是否持久化。但由于我是单元测试,我的测试中不能有外部依赖。
现在,您还需要哪些其他选项来验证用户有效的方案?由于userRepository.save()返回void,唯一的方法是验证副作用是验证模拟是否被调用。 如果我不验证模拟,那么单元测试就不会很好,因为我可以删除保存对象的行,测试仍然会通过。
对于某些返回值的模拟,情况并非如此,然后在方法中使用值。这通常意味着如果mock返回null,则应用程序抛出NullPointerException(在java的情况下)。
答案 3 :(得分:1)
了解依赖关系在测试代码中的用途非常重要:
UserFactory
示例)现在,如果依赖(mock)无法提供帮助,显然已经过测试的依赖于此帮助的代码也会失败。在这种情况下,进行额外测试以验证依赖性被调用,并不是非常有益。没有呼叫会导致在依赖于它的测试中出现多次失败,并且有明确的消息。
回到你的工厂示例,当它没有被调用时会发生什么?其他一些测试,可能是那些验证用户是保存在某个地方,还是用它做了什么的测试将失败。
自然存在第二组依赖项,它们根本不会影响您的代码,而是在后台静默执行。然而,这些很可能会反映在你的代码职责中,比如说:
SaveUser方法应该在存储库和日志操作结果
中保存新用户
通常,除了验证是否调用了适当的方法之外,没有其他方法可以进行行为检查。
作为结论,在确定是否应该编写测试时需要考虑两个问题:
除非有必要,否则不要测试您的代码是否调用其他代码; 测试您的代码是否以您认为的方式工作。