Проверка частично упорядоченных вызовов методов в JMockit

Я пытаюсь написать модульный тест (используя JMockit), который проверяет, что методы вызываются в соответствии с частичным порядком. Конкретный вариант использования — убедиться, что определенные операции вызываются внутри транзакции, но в более общем плане я хочу проверить что-то вроде этого:

  • Вызывается метод beginTransaction.
  • Методы с operation1 по 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. Я не могу заменить unverifiedInvocations на bo.operation1(); bo.operation2(), потому что это приводит к общему порядку вызовов. Правильная реализация бизнес-метода может вызвать bo.operation2(); bo.operation1().

Если я сделаю это:

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

затем я получаю ошибку «Нет непроверенных вызовов», когда someOtherOperation вызывается перед транзакцией. Попытка bo.someOtherOperation(); minTimes = 0 тоже не работает.

Итак: есть ли чистый способ указать требования частичного упорядочения для вызовов методов с использованием API ожиданий/проверок в JMockIt? Или мне нужно использовать MockClass и вручную отслеживать вызовы, а-ля:

@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);
    }
}

person Cameron Skinner    schedule 08.04.2011    source источник
comment
Verifications API поддерживает наличие нескольких блоков проверки, что было бы решением здесь: someOtherOperation следует проверять в обычном блоке перед упорядоченным блоком проверки, который содержит вызов unverifiedInvocations(). Однако в настоящее время JMockit не поддерживает эту конкретную комбинацию. Я собираюсь исправить это для версии 0.999.9.   -  person Rogério    schedule 04.05.2011
comment
Но действительно ли это поддерживает сценарий частичного заказа? Требуется, чтобы bo.operation1() и bo.operation2() выполнялись после bo.beginTransaction() и перед bo.endTransaction(), но внутри транзакции эти две операции могут выполняться в любом порядке. Это именно тот сценарий, который мне также нужно проверить.   -  person SamStephens    schedule 22.03.2013


Ответы (2)


если вам действительно нужен такой тест, то: не используйте библиотеку для насмешек, а создайте свой собственный макет с состоянием внутри, который может просто проверить правильный порядок методов. но проверка порядка вызовов обычно является плохим признаком. мой совет: не проверяйте это, рефакторинг. вы должны проверить свою логику и результаты, а не последовательность вызовов. проверьте правильность побочных эффектов (содержимое базы данных, взаимодействие со службами и т. д.). если вы тестируете последовательность, то ваш тест в основном является точной копией вашего производственного кода. так какова добавленная стоимость такого теста? и такой тест еще и очень хрупок (как любое дублирование).

возможно, вы должны сделать свой код таким:

beginTransaction()
doTransactionalStuff()
endTransaction()
doNonTransactionalStuff()
person piotrek    schedule 15.01.2014

Исходя из моего использования jmockit, я считаю, что ответ — нет даже в последней версии 1.49.

Вы можете реализовать этот тип расширенной проверки, используя расширение MockUp с некоторыми внутренними полями, чтобы отслеживать, какие функции вызываются, когда и в каком порядке.

Например, я реализовал простой MockUp для отслеживания количества вызовов методов. Цель этого примера реальна, поскольку поля Verifications и Expectations 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, может помочь переопределить метод $advice в вашем пользовательском MockUp, чтобы упростить дифференцирование и запись вызовов методов. Документы здесь: Применение рекомендаций в стиле АОП.

person jhyry-gcpud    schedule 16.07.2021