如何Java单元测试复杂类

时间:2017-09-29 15:43:32

标签: java unit-testing junit mocking junit4

我正在经历一个艰难的时期,想弄清楚如何处理单元测试的问题。我几乎没有“单元测试”经验。我正在尝试更改 classUnderTest ,只有在绝对必要时才进行最小的更改。

我正在使用JUnit 4,我愿意尝试使用MockitoJMockit或任何其他有助于高效且有效的单元测试的库。我正在为数据库使用 JDBC

问题:

classUnderTest 中,我正在访问 static 数据库。

SpecializedDao specializedDao = SpecializedDao.getSpecializedDao();
ObjectX objectResult = specializedDao.currentObjectXByTimestamp(x, x, x, x, x, x);

我正在尝试使用有效配置和无效配置的单元测试用例。解决这个问题的正确方法是什么。

我研究过的一些可能的解决方案是:

  1. 以某种方式传入,或者“注入”类 ObjectX的虚假 ObjectXResult
  2. 模拟数据库(如果使用静态数据库引用,甚至可以使用引用假的SpecializedDao类的单独的单元测试servlet?)
  3. 使用Mockito injectMocks spy 当(methodcallwithparameters)然后返回结果或其他一些实现{{3}提供。
  4. 开放给任何其他解决方案。
  5. 问题:

    classUnderTest 中,我正在使用另一个复杂的处理 class2 ,它可以完成很多工作:

    result = class2.execute(x, x, x, x, x, x);
    

    class2 处理内容并返回结果ENUM或一些异常。如何在 classUnderTest 上保留此特定单元测试的范围来处理此问题。 class2 访问数据库,并做了很多工作,这就是为什么它是自己的类,但我认为最后三个测试用例依赖于处理来彻底测试 classUnderTest

    感谢您对我的支持,我尽可能清晰地提出问题。

6 个答案:

答案 0 :(得分:3)

在任何情况下你都需要使用像Mockito这样的东西,所以我假设你有。

第一个问题:

测试涉及静态调用的代码可能很麻烦。您可以包含一些像PowerMock这样的字节代码操作库,但在我看来它并不值得。你可以做的是将静态调用放在package-local方法中,然后使用spy将其存根。类似的东西:

//in class under test:
SpecializedDato getSpecializedDao() {
    return SpecializedDao.getSpecializedDao();
}

//in test:
import static org.mockito.Mockito.*;
//...
final SpecializedDao daoMock = mock(SpecializedDao.class);
final ClassUnderTest classUnderTest = spy(new ClassUnderTest());
doReturn(daoMock).when(classUnderTest).getSpecializedDao();

第二个问题:

如果您发现测试变得非常复杂,可能是由于被测试的课程过于复杂。看看你是否可以将功能提取到其他较小的类中。然后,您只需要验证是否正在调用这些较小的类。

答案 1 :(得分:3)

一个好的规则是永远不要从JUnits连接到外部源。 任何可以做的事情,例如数据库连接,你都可以使用Mockito来模拟它们。

使用Mockito,您可以模拟您不关心的所有课程。在你的情况下,你可以模拟那些沉重的类并返回预期的结果。

答案 2 :(得分:1)

在我的办公室,我们通常会使用一些解决方案来使复杂的课程更具可测性。

最不可取的是唯一能够在不修改被测试的类的情况下工作的人 - 重度嘲弄。你嘲笑你班级以外的一切。如果你在课堂上有一个静态成员,你可以在开始之前将它设置为带有反射的模拟(可能是你问题的答案)。

这种方法既困难又脆弱,但如果你不能修改被测试的类,这几乎是唯一可行的方法。

一个小的例外是一个稍微不同的模拟版本 - 扩展测试中的类并覆盖您的类与之交互的所有内容。这对于静力学并不总是有效,但如果你的课程是用吸气剂设计的,那么效果很好。

如果您可以修改课程,我们会使用一些技巧来帮助您:

1)始终避免使用静力学。如果您必须有一个单身,请确保它可以通过使用注射框架或自己构建迷你注射框架来测试(静态预先填充的地图应该是一个好的开始)

2)我们将大量业务逻辑放在纯函数中,并调用包含所有逻辑“规则”类的类。我们的规则通常可以直接追溯到需求,并且它们不提供任何自己的数据,所有内容都被传入。它们通常由包含相关数据和其他代码的OO外观调用。

3)降低课程的复杂性和互动性。将数据库访问与计算分开。看看每个班级,并确保它完成一件事。这没有直接帮助,但你会注意到,如果遵循这条规则,编写测试会更简单。

答案 3 :(得分:1)

如果没有在模拟框架上使用解决方案,处理仅涉及对CuT的最小更改的静态的标准模式是提取静态方法以位于接口后面,并且最初访问静态方法的类现在访问方法在界面上。这个接口的一个实例被注入到CuT中,当没有测试时,它是一个小包装类,它直接委托给原始的静态方法。

现在你有一个可以使用的接口,你可以通过setter或构造函数注入注入该接口的实例,而Bob是你的叔叔。

这个标准技巧确实涉及一些额外的东西" - 一个接口,一个实现委托给静态接口的小包装类,以及一些代码或DI框架接线,它们在代码的生成部分中实现类,通过构造函数或setter注入CuT。

然后在你的单元测试中,你有一个钩子来注入一个模拟,一个手工创建的存根,或者你想要的任何样式的测试双。

这是一种非常常见的模式,当您处理不受控制的第三方库时,它喜欢使用静态。

答案 4 :(得分:1)

我最近在youtube上发布了一段视频,展示了Jim Weaver提到的一些步骤。它是德语,但可能仍然有用。在这个视频中,我介绍了一个接口来克服静态调用数据库访问的问题。

https://www.youtube.com/watch?v=KKYro-HGRyk

如果您无法更改所调用方法的源代码,则第二个视频演示了一种有用的方法。

https://www.youtube.com/watch?v=fFLfx8qsBdI

答案 5 :(得分:1)

如果您关心开发良好,有用的测试,我的建议是模拟数据库或您的代码可能与之交互的其他复杂类。

相反,请考虑您要测试的代码要提供的特定业务场景,并编写逼真的(即,不用廉价 - 通常是不正确的 - 替换真实的类/组件他们)和有意义的(从现实世界的要求的角度来看)测试每个场景。

那就是说,如果您仍想模拟DAO类,可以使用JMockit进行如下操作:

@Test
public void exampleTest(@Mocked SpecializedDao mockDao) {
    ObjectX objectResult = new ObjectX();

    new Expectations() {{
        mockDao.currentObjectXByTimestamp(anyX, anyY, anyZ); result = objectResult;
    }};


    // Call the SUT.
}

anyX等不是实际的JMockit参数匹配字段,当然 - 实际的是anyIntanyStringany等。< / p>