【问题标题】:Encapsulating and mocking封装和模拟
【发布时间】:2017-09-06 16:24:12
【问题描述】:

假设我有一个简单依赖的类:

public interface Dependency {
    int doSomething(int value);
    void doMore(int value);
    int doALotMore(int value);
}

public final class A implements SomeInterface {
    private final Dependency dep;

    public A (Dependency dep) {
        this.dep = dep;
    }

    @Override
    public int add(final int x) {
        dep.doMore(x);
        return x + dep.doSomething(x) + dep.doALotMore(x);
    }
}

我正在使用模拟编写测试:

public class TestA {
    private Dependency mockDep;
    private SomeInterface a;

    @Before
    public void setUp() {
        mockDep = Mockito.mock(Dependency.class);
        a = new A(mockDep);
    }

    @Test
    public void shouldAdd() {
        final int x = 5;
        when(mockDep.doSomething(x)).thenReturn(6);
        when(mockDep.doALotMore(x)).thenReturn(7);

        int actual = a.add(x);

        assertThat(actual, is(18));

        verify(mockDep, times(1)).doSomething();
        verify(mockDep, times(1)).doALotMore();
        verify(mockDep, times(1)).doMore();

        verifyNoMoreInteractions(mockDep);
    }
}

到目前为止一切顺利。

所以问题是:我们是否通过验证依赖项的使用方式违反了A 类的封装?是否真的需要测试以这种方式使用的依赖项?我们不应该像黑盒一样测试A(从测试用例中删除verify 调用,只留下assertThat)吗?在这种情况下如何处理依赖关系?

我问的原因是我发现自己编写了大量的验证依赖代码,似乎我们开始测试有关类的实际内部实现细节。我对此感到不舒服,因为当我尝试以另一种方式重写此实现细节时,我需要重写测试用例,尽管例如add 的结果将是相同的。如果我将我的类作为黑盒进行测试,我可以更改实现细节并仍然确保给定的输入会给出相同的输出。

或者有必要对实现细节进行实际测试,这就是单元测试本身的意义所在?这对我来说似乎有些不对劲。

请考虑这个测试:

 public class TestA {
    private Dependency mockDep;
    private SomeInterface a;
    private final int x = 5;

    @Before
    public void setUp() {
        mockDep = Mockito.mock(Dependency.class);
        a = new A(mockDep);

        when(mockDep.doSomething(x)).thenReturn(6);
        when(mockDep.doALotMore(x)).thenReturn(7);
    }

    @Test
    public void shouldAdd() {
        int actual = a.add(x);

        assertThat(actual, is(18));
    }
}

【问题讨论】:

  • 如果 A#add(int) 被定义为将特定方法调用的结果添加到 x 本身的方法,则可以测试它是否实际调用了该方法并将其添加到 @987654332 @。 When 和 where add 调用依赖类的方法实际上对您的测试无关紧要,并且您无论如何都不会测试它(在您的示例中)。因此,该测试对我来说看起来不错。你有一个你觉得不舒服的测试用例的例子吗? (verifyNoMoreInteractions(mockDep); 的用法可以有争议,但这取决于测试对象)。
  • 单元测试是灰盒/白盒测试。它不应该被用作黑盒测试。它的目的是测试测试中类的所有逻辑,包括它如何与其依赖项交互。模拟有助于避免测试任何依赖代码。
  • 根据经验,您希望验证交互或存根呼叫。如果你发现自己两者都做,这通常是一个不好的迹象。例如,在这种情况下,您希望存根行为。如果您的模拟没有被调用,那么答案将是错误的,不需要确保您的存根已被调用。如果您想确保没有发生其他交互,我建议启用严格模式 (static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/…)。

标签: java unit-testing testing mocking encapsulation


【解决方案1】:

这实际上取决于您正在测试的逻辑。由于您的示例未提供任何上下文,因此当我对测试此类交互感到满意时,我会给您一个案例,甚至是强制性的:

假设您正在测试身份验证令牌验证。您将一些令牌传递给您的验证器,它会返回 true/false。在您的验证器内部,您正在调用一些 jwt.validate 或任何其他第 3 方哈希验证方法。在这种情况下,我需要知道每次都会调用这个验证器,因为我可以在里面引入一些if token == null 条件,它会绕过这个验证调用并返回 false。那么您的测试仍然可以通过,但您的代码现在容易受到计时攻击。

这是一种例子。我喜欢用这种方式测试的另一种类型的测试是所谓的border testing。我想知道我的课程触发了条带支付网关 - 所以我模拟它并确保它被调用而不检查这个特定测试中的任何复杂内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-10
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 2016-11-03
    相关资源
    最近更新 更多