1

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

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

期望和验证 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情况。我无法替换为unverifiedInvocationsbo.operation1(); bo.operation2()因为这会对调用进行总排序。业务方法的正确实现可以调用bo.operation2(); bo.operation1().

如果我做到了:

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

someOtherOperation然后在事务之前调用时出现“没有未验证的调用”失败。尝试bo.someOtherOperation(); minTimes = 0也无济于事。

那么:有没有一种简洁的方法来使用 JMockIt 中的 Expectations/Verifications API 来指定方法调用的部分排序要求?或者我是否必须使用 aMockClass并手动跟踪调用,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);
    }
}
4

2 回答 2

1

if you really need such test then: don't use mocking library but create your own mock with state inside that can simply check the correct order of methods. but testing order of invocations is usually a bad sign. my advice would be: don't test it, refactor. you should test your logic and results rather than a sequence of invocations. check if side effects are correct (database content, services interaction etc). if you test the sequence then your test is basically exact copy of your production code. so what's the added value of such test? and such test is also very fragile (as any duplication).

maybe you should make your code looks like that:

beginTransaction()
doTransactionalStuff()
endTransaction()
doNonTransactionalStuff()
于 2014-01-15T21:13:39.647 回答
1

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

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

例如,我实现了一个简单MockUp的跟踪方法调用计数的方法。这个例子的目的是真实的,因为VerificationsandExpectations times字段在模拟 a 时不起作用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 尝试做的事情的复杂性,它可能有助于覆盖$advice您自定义中的方法MockUp以简化区分和记录方法调用。此处的文档:应用 AOP 风格的建议

于 2021-07-16T20:13:54.620 回答