имитация или заглушка для связанного вызова

protected int parseExpire(CacheContext ctx) throws AttributeDefineException {
    Method targetMethod = ctx.getTargetMethod();
    CacheEnable cacheEnable = targetMethod.getAnnotation(CacheEnable.class);
    ExpireExpr cacheExpire = targetMethod.getAnnotation(ExpireExpr.class);
    // check for duplicate setting
    if (cacheEnable.expire() != CacheAttribute.DO_NOT_EXPIRE && cacheExpire != null) {
        throw new AttributeDefineException("expire are defined both in @CacheEnable and @ExpireExpr");
    }
    // expire time defined in @CacheEnable or @ExpireExpr
    return cacheEnable.expire() != CacheAttribute.DO_NOT_EXPIRE ? cacheEnable.expire() : parseExpireExpr(cacheExpire, ctx.getArgument());
}

это метод проверки,

Method targetMethod = ctx.getTargetMethod();
CacheEnable cacheEnable = targetMethod.getAnnotation(CacheEnable.class);

Мне нужно издеваться над тремя CacheContext, Method и CacheEnable. Есть ли идея сделать тестовый пример намного проще?


person jilen    schedule 28.10.2011    source источник


Ответы (4)


Mockito может обрабатывать связанные заглушки:

Foo mock = mock(Foo.class, RETURNS_DEEP_STUBS);

// note that we're stubbing a chain of methods here: getBar().getName()
when(mock.getBar().getName()).thenReturn("deep");

// note that we're chaining method calls: getBar().getName()
assertEquals("deep", mock.getBar().getName());

Насколько мне известно, первый метод в цепочке возвращает макет, который настроен на возврат вашего значения при вызове второго связанного метода.

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

person Lunivore    schedule 28.10.2011
comment
Что ж, Щепан создал Mockito, потому что он увидел, как я и некоторые другие запускали свои собственные макеты вручную вместо использования EasyMock, и решил, что макеты должны работать лучше для BDD — поэтому, очевидно, я предпочитаю Mockito! Но для этого он разветвил EasyMock, поэтому да, EasyMock великолепен. Мы стоим на плечах гигантов... - person Lunivore; 31.10.2011
comment
Это интересно, и я проголосовал. Но разве вы не можете просто использовать Foo foo=mock(Foo.class); Bar bar=mock(Bar.class); when(foo.getBar()).thenReturn(bar); when(bar.getName()).thenReturn("deep")'. На мой взгляд, это легко читается и не требует понимания концепции ГЛУБОКОЙ заглушки. (Кстати, мне нравится Mockito.) - person cdunn2001; 08.04.2013
comment
Ага. Я думаю, что это может быть полезно, если ваш следующий шаг в устаревшем коде состоит в том, чтобы переместить один из методов в нужное место (что полностью устраняет проблему, но впоследствии потребует большого количества исправлений для этого способа). - person Lunivore; 08.04.2013
comment
Но всегда ли это черта зависти? У меня есть дерево dto, которое упорядочивает мои большие данные в памяти. У меня есть другие классы, которые читают данные из этого дерева и выполняют над ними операции. Чтобы протестировать их, мне нужно смоделировать dto — кстати, я могу создать фактический dto, но это так же громоздко — глубокая заглушка действительно помогает мне в этом случае. Я что-то пропустил здесь? могу ли я улучшить свой код? - person Arash; 28.02.2014
comment
@Arash Это не всегда зависть к функциям. В вашем случае я бы посмотрел на создание какого-то шаблона MVP, где модель представлена ​​​​в конкретном DTO на уровне сохранения. Теперь у вас есть богатый объект, а не просто DTO. Такие шаблоны, как MVP, применимы как к сохраняемости, так и к пользовательским интерфейсам! - person Lunivore; 28.02.2014
comment
Это не работает, если одна из цепочек возвращает универсальный тип. Кто-нибудь еще сталкивался с этой проблемой? - person Vivek Kothari; 04.11.2015
comment
@VivekKothari Может быть, отправить его команде Mockito? github.com/mockito/mockito/issues - person Lunivore; 05.11.2015
comment
Определение Feture envy было перенесено сюда: github.com/troessner/reek /blob/master/docs/Feature-Envy.md - person Sammi; 13.10.2016
comment
@Sammi Это выглядит как великолепный маленький инструмент; Я не встречал Река раньше. И какое фантастическое имя... :D - person Lunivore; 13.10.2016
comment
@Lunivore Не могли бы вы указать какой-нибудь официальный источник, авторы которого заявляют, что глубокие заглушки следует использовать только для устаревшего кода? - person Magnilex; 27.02.2017
comment
@Magnilex На самом деле это в официальном источнике. Как и исходный код. Обратите внимание, что в большинстве случаев мок, возвращающий мок, неверен. Также: ВНИМАНИЕ: эта функция редко требуется для обычного чистого кода! Оставьте это для устаревшего кода. Насмешка над имитацией, чтобы вернуть насмешку, вернуть насмешку, (...), вернуть что-то значимое, намекает на нарушение Закона Деметры или насмешку над объектом-значением (хорошо известный анти-шаблон). github.com/mockito/mockito/ blob/master/src/main/java/org/ (большая часть в строке 1393). - person Lunivore; 27.02.2017
comment
@Lunivore Справедливая точка. Хотя примерно один уровень этого может быть приемлемым, но я понимаю, что этот метод, вероятно, широко используется. Интересно, является ли это кандидатом на устаревание. - person Magnilex; 27.02.2017
comment
@Magnilex Обертывание тестов вокруг устаревшего кода является важной частью возможности безопасного рефакторинга. Нет ничего плохого в том, чтобы использовать его в качестве промежуточного шага к более чистой кодовой базе. - person Lunivore; 27.02.2017
comment
@Magnilex Сказав это, я на самом деле использую RETURNS_DEEP_STUBS в данный момент для поддержки сложного интерфейса с некоторыми методами, которые пересылают внутреннюю хэш-карту, которую я не хочу высмеивать, используя Kotlin и ключевое слово by ... так что, возможно в наши дни для этого есть больше применений. - person Lunivore; 27.02.2017
comment
Ссылка вверху ответа не работает. - person ajb; 04.01.2018
comment
@ajb Спасибо; исправлено. - person Lunivore; 04.01.2018
comment
В итоге я использовал RETURNS_SELF, так как официальная документация не одобряет RETURNS_DEEP_STUBS - person Ming; 13.06.2019
comment
@Ming RETURNS_SELF полезен, если вы создаете сборщиков (которые обычно возвращают себя, чтобы вы могли продолжать добавлять что-то). В случае ОП это не соответствует подписи или намерению. RETURNS_DEEP_STUBS — это то, что нужно использовать, особенно если цель состоит в том, чтобы обернуть что-то в тест при рефакторинге. Документы на самом деле не препятствуют RETURNS_DEEP_STUBS; они препятствуют антипаттерну, который в первую очередь требует этого (нарушение Закона Деметры, также известное как зависть). - person Lunivore; 14.06.2019
comment
Как добиться этого с помощью аннотации @Mock? - person Ruifeng Ma; 28.08.2019
comment
@RuifengMa Из этой документации я предполагаю, что это будет @Mock (answer = RETURNS_DEEP_STUBS) - попробуйте и дайте нам знать! static.javadoc.io/org. mockito/mockito-core/2.2.28/org/mockito/ - person Lunivore; 28.08.2019

На всякий случай, если вы используете Kotlin. MockK ничего не говорит о том, что цепочка является плохой практикой, и легко позволяет вам сделать это.

val car = mockk<Car>()

every { car.door(DoorType.FRONT_LEFT).windowState() } returns WindowState.UP

car.door(DoorType.FRONT_LEFT) // returns chained mock for Door
car.door(DoorType.FRONT_LEFT).windowState() // returns WindowState.UP

verify { car.door(DoorType.FRONT_LEFT).windowState() }

confirmVerified(car)
person yuranos    schedule 24.01.2019
comment
Да, отличная вещь, действительно помогает уменьшить количество шаблонного кода. - person AbstractVoid; 02.04.2019

Мое предложение упростить ваш тестовый пример — реорганизовать ваш метод.

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

В данном случае это потому, что у вас есть цепочка методов, которая уходит на несколько уровней вглубь. Возможно, передайте ctx, cacheEnable и cacheExpire в качестве параметров.

person Doug R    schedule 28.10.2011
comment
Да, но эти поля взяты из контекста aop во время выполнения, поэтому среду сложно упростить. - person jilen; 31.10.2011
comment
В JMockit есть способы сделать это. Вы можете имитировать поля в своих объектах, имитируя внедрение полей АОП. Или вы можете использовать методы деинкапсуляции, инициализирующие поля provate с помощью фиктивных экземпляров. - person Konstantin Pribluda; 31.10.2011
comment
Можете ли вы привести пример @doug, пожалуйста? - person ScottyBlades; 22.09.2020

Я нашел JMockit проще в использовании и полностью переключился на него. См. тестовые примеры с его использованием:

https://github.com/ko5tik/andject/blob/master/src/test/java/de/pribluda/android/andject/ViewInjectionTest.java

Здесь я высмеиваю базовый класс Activity, который исходит от Android SKD и полностью заглушен. С JMockit вы можете издеваться над вещами, которые являются окончательными, частными, абстрактными или чем-то еще.

В вашем тестовом примере это будет выглядеть так:

public void testFoo(@Mocked final Method targetMethod, 
                    @Mocked  final CacheContext context,
                    @Mocked final  CacheExpire ce) {
    new Expectations() {
       {
           // specify expected sequence of infocations here

           context.getTargetMethod(); returns(method);
       }
    };

    // call your method
    assertSomething(objectUndertest.cacheExpire(context))
person Konstantin Pribluda    schedule 28.10.2011
comment
думает о вашем ответе, но я все равно использую mockito. - person jilen; 28.10.2011
comment
Обратите внимание, что JMockit имеет аннотацию специально для связанных вызовов: @Cascading. Кроме того, в подобных случаях вы, вероятно, захотите использовать NonStrictExpectations вместо Expectations, предполагая, что вызовы имитируемых методов не нуждаются в проверке. - person Rogério; 31.10.2011
comment
Спасибо, я пропустил эту аннотацию;) Упростил свои модульные тесты. - person Konstantin Pribluda; 31.10.2011