经常被告知编写单元测试一个人必须仅测试一个类并模拟所有协作者。我正在尝试学习TDD以使我的代码设计更好,现在我陷入了应该打破该规则的情况。还是不是吗?
一个示例:正在测试的类具有获得Person
的方法,基于Person
创建Employee
并返回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());
}
如您所见,我必须使用未进行测试的Employee
类的coolestEmployee.getName()
。
想到的一种可能的解决方案是将Person
s转换为Employee
s的任务为Employee
类的新方法,例如
public Employee createFromPerson(Person person);
我想过问题吗?什么是正确的方法?
单位测试的目标是快速可靠地确定单个系统是否破坏。这并不意味着您需要模拟周围的整个世界,只是您应该确保使用的合作者快速,确定性和经过良好的测试。
数据对象 - 尤其是生成的值对象,旨在稳定且经过良好测试,依赖关系很少。像其他严重的物体一样,它们也倾向于模拟,因为模拟框架不会对状态具有强大的控制(例如,getX
应在setX(n)
之后返回n
)。假设员工是一个数据对象,只要对其包含的任何逻辑进行了充分测试,它可能是单位测试中实际使用的好候选者。
其他合作者一般不要模拟:
- JRE类和接口。(例如。
- 确定性的第三方类。(如果任何类别或方法更改为
final
,您的模拟都会失败;此外,如果您使用库的稳定版本,也不是失败的失败来源。) - 状态类,仅仅是因为模拟在测试交互作用方面比状态要好得多。改为考虑假货或其他一些测试双重。
- 快速且经过充分测试的其他类别几乎没有依赖性。如果您对系统有信心,并且对您的测试的确定性或速度没有危害,则无需嘲笑它。
那留下了什么?非确定性或缓慢的服务类别或您所写的包装器,无状态或在测试期间变化很小,并且可能有许多合作者的合作者自己的。在这些情况下,很难使用实际类编写快速,确定性的测试,因此使用测试双重的double非常有意义,而且使用模仿框架创建一个非常容易。p> 另请参见: Martin Fowler的文章" Mocks Not Subs",该文章谈论各种测试双打及其优势和缺点。
获得仅读取私有字段的获取通常不值得测试。默认情况下,您可以在其他测试中非常安全地依靠它们。因此,我不必担心在AmployeManager的测试中使用dankestPerson.getName()
。
就测试而言,测试没有错。生产代码的设计可能是不同的 - 嘲笑dankestPerson
可能意味着它具有接口或抽象基类,这可能是过度工程设计的迹象,尤其是对于业务实体而言。相反,我要做的只是新的Person
,将其名称设置为预期值并设置dataMinerMock
返回。
另外,在班级名称中使用"经理"可能表明缺乏凝聚力,并且过于广泛的职责。