【问题标题】:Verifying partially ordered method invocations in JMockit在 JMockit 中验证部分有序的方法调用
【发布时间】:2011-04-08 20:23:06
【问题描述】:

我正在尝试编写一个单元测试(使用 JMockit)来验证方法是根据部分顺序调用的。具体的用例是确保在事务中调用某些操作,但更一般地说,我想验证这样的事情:

  • 方法beginTransaction被调用。
  • 方法 operation1operationN 以任意顺序调用。
  • 方法endTransaction被调用。
  • 方法 someOtherOperation 在事务之前、期间或之后的某个时间被调用。

Expectations 和 Verifications API 似乎无法处理此要求。

如果我有 @Mocked BusinessObject bo,我可以验证是否调用了正确的方法(以任何顺序):

new Verifications() {{
    bo.beginTransaction();
    bo.endTransaction();
    bo.operation1();
    bo.operation2();
    bo.someOtherOperation();
}};

可选择将其设为FullVerifications 以检查是否没有其他副作用。

要检查排序约束,我可以这样做:

new VerificationsInOrder() {{
    bo.beginTransaction();
    unverifiedInvocations();
    bo.endTransaction();
}};

但这不能处理someOtherOperation 的情况。我不能用bo.operation1(); bo.operation2() 替换unverifiedInvocations,因为这会对调用进行total 排序。业务方法的正确实现可以调用bo.operation2(); bo.operation1()

如果我成功了:

new VerificationsInOrder() {{
    unverifiedInvocations();
    bo.beginTransaction();
    unverifiedInvocations();
    bo.endTransaction();
    unverifiedInvocations();
}};

然后,当在事务之前调用 someOtherOperation 时,我会收到“没有未验证的调用”失败。尝试bo.someOtherOperation(); minTimes = 0 也不起作用。

那么:有没有一种简洁的方法来使用 JMockIt 中的 Expectations/Verifications API 指定方法调用的部分排序要求?还是我必须使用MockClass 并手动跟踪调用,a la

@MockClass(realClass = BusinessObject.class)
public class MockBO {
    private boolean op1Called = false;
    private boolean op2Called = false;
    private boolean beginCalled = false;

    @Mock(invocations = 1)
    public void operation1() {
        op1Called = true;
    }

    @Mock(invocations = 1)
    public void operation2() {
        op2Called = true;
    }

    @Mock(invocations = 1)
    public void someOtherOperation() {}

    @Mock(invocations = 1)
    public void beginTransaction() {
        assertFalse(op1Called);
        assertFalse(op2Called);
        beginCalled = true;
    }

    @Mock(invocations = 1)
    public void endTransaction() {
        assertTrue(beginCalled);
        assertTrue(op1Called);
        assertTrue(op2Called);
    }
}

【问题讨论】:

  • Verifications API 支持有多个验证块,这将是这里的解决方案:someOtherOperation 应在常规块中验证包含调用的有序验证块中unverifiedInvocations()。但是,JMockit 目前不支持这种特定组合。我将尝试在 0.999.9 版本中修复它。
  • 但这真的支持偏序场景吗?要求 bo.operation1() 和 bo.operation2() 必须都发生在 bo.beginTransaction() 之后和 bo.endTransaction() 之前,但是在事务中这两个操作可以以任何顺序发生。这正是我还需要测试的场景。

标签: java unit-testing mocking jmockit


【解决方案1】:

如果你真的需要这样的测试,那么:不要使用模拟库,而是创建你自己的模拟,里面有状态,可以简单地检查方法的正确顺序。 但是测试调用顺序通常是一个不好的迹象。我的建议是:不要测试它,重构。您应该测试您的逻辑和结果,而不是一系列调用。检查副作用是否正确(数据库内容、服务交互等)。如果您测试序列,那么您的测试基本上是生产代码的精确副本。那么这种测试的附加值是什么?而且这样的测试也非常脆弱(就像任何重复一样)。

也许你应该让你的代码看起来像这样:

beginTransaction()
doTransactionalStuff()
endTransaction()
doNonTransactionalStuff()

【讨论】:

    【解决方案2】:

    根据我对 jmockit 的使用,我相信即使在最新版本 1.49 中,答案也是否定的。

    您可以使用带有一些内部字段的MockUp 扩展来实现这种类型的高级验证,以跟踪哪些函数被调用、何时以及以什么顺序调用。

    例如,我实现了一个简单的MockUp 来跟踪方法调用计数。这个例子的目的是真实的,因为 VerificationsExpectations times 字段在模拟 ThreadGroup 时不起作用(对其他敏感类型也有用):

    public class CalledCheckMockUp<T> extends MockUp<T>
    {
        private Map<String, Boolean> calledMap = Maps.newHashMap();
        private Map<String, AtomicInteger> calledCountMap = Maps.newHashMap();
        
        public void markAsCalled(String methodCalled)
        {
            if (methodCalled == null)
            {
                Log.logWarning("Caller attempted to mark a method string" +
                               " that is null as called, this is surely" +
                               " either a logic error or an unhandled edge" +
                               " case.");
            }
            else
            {
                calledMap.put(methodCalled, Boolean.TRUE);
                calledCountMap.putIfAbsent(methodCalled, new AtomicInteger()).
                    incrementAndGet();
            }
        }
    
        public int methodCallCount(String method)
        {
            return calledCountMap.putIfAbsent(method, new AtomicInteger()).get();
        }
        
        public boolean wasMethodCalled(String method)
        {
            if (method == null)
            {
                Log.logWarning("Caller attempted to mark a method string" +
                               " that is null as called, this is surely" +
                               " either a logic error or an unhandled edge" +
                               " case.");
                return false;
            }
    
            return calledMap.containsKey(method) ? calledMap.get(method) :
                Boolean.FALSE;
        }
    }
    

    使用如下所示,其中 cut1 是包装实际 ThreadGroup 的动态代理类型:

    String methodId = "activeCount";
    
    CalledCheckMockUp<ThreadGroup> calledChecker = new CalledCheckMockUp<ThreadGroup>()
        {
            @Mock
            public int activeCount()
            {
                markAsCalled(methodId);
                return active;
            }
        };
    
    . . .
    
    int callCount = 0;
    int activeCount = cut1.activeCount();
    callCount += 1;
    
    Assertions.assertTrue(calledChecker.wasMethodCalled(methodId));
    Assertions.assertEquals(callCount, calledChecker.methodCallCount(methodId));
    

    我知道问题很老,这个例子不完全适合 OP 的用例,但希望它可以帮助引导其他人找到一个潜在的解决方案(或者 OP,上帝保佑这仍然没有解决一个重要的用途情况,这不太可能)。

    鉴于 OP 尝试做的事情的复杂性,在您的自定义 MockUp 中覆盖 $advice 方法可能有助于简化区分和记录方法调用。此处的文档:Applying AOP-style advice

    【讨论】:

      猜你喜欢
      • 2016-03-05
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多