如何在隔离测试类的同时测试对象是否正确创建?

时间:2016-06-22 13:10:22

标签: unit-testing junit tdd mockito

经常被告知编写单元测试必须只测试一个类并模拟所有协作者。我正在尝试学习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);

我是否在思考这个问题?什么是正确的方法?

2 个答案:

答案 0 :(得分:4)

单元测试的目标是快速可靠地确定单个系统是否损坏。这并不意味着你需要模拟它周围的整个世界,只是你应该确保你使用的合作者快速,确定,并经过充分测试。

数据对象 - 特别是POJO和生成的值对象 - 往往是稳定且经过良好测试的,具有非常少的依赖性。像其他重度有状态的对象一样,它们也往往非常繁琐,因为模拟框架往往不具有对状态的强大控制(例如getX应该在{{1}之后返回n }})。假设Employee是一个数据对象,它可能是单元测试中实际使用的一个很好的候选者,只要它包含的任何逻辑都经过充分测试。

其他合作者一般不要嘲笑:

  • JRE类和接口。 (例如,永远不要嘲笑一个列表。它将无法阅读,而且你的测试不会更好。)
  • 确定性第三方课程。 (如果任何类或方法更改为setX(n),则您的模拟将失败;此外,如果您使用的是库的稳定版本,则它也不是虚假的失败源。)< / LI>
  • 有状态的类,只是因为模拟测试交互比状态好得多。考虑假冒或其他一些测试。
  • 快速且经过良好测试的其他类很少依赖。如果您对系统有信心,并且对测试的确定性或速度没有任何危害,则无需嘲笑它。

这会留下什么?您已经写过的非确定性或缓慢的服务类或包装,无状态或在测试期间变化很小,并且可能有许多协作者他们自己的。在这些情况下,使用实际类很难编写快速且确定性的测试,因此使用测试双重很有意义 - 并且使用模拟框架创建一个非常容易。

参见: Martin Fowler的文章"Mocks Aren't Stubs",其中讨论了各种测试双打以及它们的优点和缺点。

答案 1 :(得分:0)

只读取私有字段的getters通常不值得测试。默认情况下,您可以在其他测试中非常安全地依赖它们。因此,我不担心在EmployeeManager的测试中使用dankestPerson.getName()

就测试而言,你的测试没有任何问题。生产代码的设计可能不同 - 模拟dankestPerson可能意味着它具有接口或抽象基类,这可能是过度工程的标志,尤其是对于业务实体。我要做的只是新增Person,将其名称设置为预期值并设置dataMinerMock以返回它。

此外,在课程名称中使用“经理”可能表示缺乏凝聚力,责任范围太广。