经常被告知编写单元测试必须只测试一个类并模拟所有协作者。我正在尝试学习TDD以使我的代码设计更好,现在我遇到了这个规则应该被打破的情况。或者不应该?
示例:测试中的类有一个获取Person
的方法,根据Employee
创建Person
并返回Employee
。
public class EmployeeManager {
private DataMiner dataMiner;
public Employee getCoolestEmployee() {
Person dankestPerson = dataMiner.getDankestPerson();
Employee employee = new Employee();
employee.setName(dankestPerson.getName() + "bug in my code");
return employee;
}
// ...
}
Employee
应该被视为合作者吗?如果没有,为什么不呢?如果是的话,我该如何正确地测试那个'员工'是否正确创建?
这是我想到的测试(使用JUnit和Mockito):
@Test
public void coolestEmployeeShouldHaveDankestPersonsName() {
when(dataMinerMock.getDankestPerson()).thenReturn(dankPersonMock);
when(dankPersonMock.getName()).thenReturn("John Doe");
Employee coolestEmployee = employeeManager.getCoolestEmployee();
assertEquals("John Doe", coolestEmployee.getName());
}
如您所见,我必须使用coolestEmployee.getName()
- 未受测试的Employee
类的方法。
我想到的一个可能的解决方案是将Person
转换为Employee
的任务转换为Employee
类的新方法,类似
public Employee createFromPerson(Person person);
我是否在思考这个问题?什么是正确的方法?
答案 0 :(得分:4)
单元测试的目标是快速可靠地确定单个系统是否损坏。这并不意味着你需要模拟它周围的整个世界,只是你应该确保你使用的合作者快速,确定,并经过充分测试。
数据对象 - 特别是POJO和生成的值对象 - 往往是稳定且经过良好测试的,具有非常少的依赖性。像其他重度有状态的对象一样,它们也往往非常繁琐,因为模拟框架往往不具有对状态的强大控制(例如getX
应该在{{1}之后返回n
}})。假设Employee是一个数据对象,它可能是单元测试中实际使用的一个很好的候选者,只要它包含的任何逻辑都经过充分测试。
其他合作者一般不要嘲笑:
setX(n)
,则您的模拟将失败;此外,如果您使用的是库的稳定版本,则它也不是虚假的失败源。)< / LI>
这会留下什么?您已经写过的非确定性或缓慢的服务类或包装,无状态或在测试期间变化很小,并且可能有许多协作者他们自己的。在这些情况下,使用实际类很难编写快速且确定性的测试,因此使用测试双重很有意义 - 并且使用模拟框架创建一个非常容易。
参见: Martin Fowler的文章"Mocks Aren't Stubs",其中讨论了各种测试双打以及它们的优点和缺点。
答案 1 :(得分:0)
只读取私有字段的getters通常不值得测试。默认情况下,您可以在其他测试中非常安全地依赖它们。因此,我不担心在EmployeeManager的测试中使用dankestPerson.getName()
。
就测试而言,你的测试没有任何问题。生产代码的设计可能不同 - 模拟dankestPerson
可能意味着它具有接口或抽象基类,这可能是过度工程的标志,尤其是对于业务实体。我要做的只是新增Person
,将其名称设置为预期值并设置dataMinerMock
以返回它。
此外,在课程名称中使用“经理”可能表示缺乏凝聚力,责任范围太广。