макет або заглушка для ланцюгового дзвінка


76
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. Чи є ідея спростити тестовий випадок?

Відповіді:


160

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());

AFAIK, перший метод у ланцюжку повертає макет, який налаштований на повернення вашого значення при другому виклику ланцюгового методу.

Автори Mockito зазначають, що це слід використовувати лише для застарілого коду . Краще зробити інакше - ввести поведінку у свій CacheContext та надати будь-яку інформацію, необхідну для самої роботи. Кількість інформації, яку ви витягуєте з CacheContext, говорить про те, що ваш клас має заздрість до можливостей .


Ну, Щепан створив Mockito, тому що бачив, як я та деякі інші виконували наші власні макети вручну замість того, щоб використовувати EasyMock, і вирішив, що макети повинні працювати краще для BDD - тому, очевидно, я віддаю перевагу Mockito! Але він підгалужив EasyMock це зробити, тому з цієї причини, так, EasyMock чудовий. Ми стоїмо на плечах велетнів ...
Lunivore

Це цікаво, і я підтримав. Але не можна просто використовувати Foo foo=mock(Foo.class); Bar bar=mock(Bar.class); when(foo.getBar()).thenReturn(bar); when(bar.getName()).thenReturn("deep")'. На мій погляд, це легко читати і не вимагає розуміння концепції "ГЛИБОКОГО" заглушення. (До речі, мені подобається Mockito.)
cdunn2001,

1
Це не працює, якщо одна з ланцюжків повертає загальний тип. Хтось ще стикався з цією проблемою?
Вівек Котарі

4
@Magnilex Це насправді в офіційному джерелі. Як і у вихідному коді. "Зверніть увагу, що в більшості випадків макет, що повертає макет, є помилковим." Також: ПОПЕРЕДЖЕННЯ: Ця функція рідко потрібна для звичайного чистого коду! Залиште це для застарілого коду. Знущання над знущанням, щоб повернути знущання, (з ... github.com/mockito/mockito/blob/master/src/main/java/org/… (більша частина - у рядку 1393).
Lunivore

1
@RuifengMa З цієї документації я здогадуюсь, що це буде @Mock (відповідь = RETURNS_DEEP_STUBS) - дайте йому знати! static.javadoc.io/org.mockito/mockito-core/2.2.28/org/mockito/…
Lunivore

6

Про всяк випадок, коли ви використовуєте 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)

Так, чудово, дійсно допомагає зменшити кількість типових кодів.
AbstractVoid

3

Моя порада спростити ваш тест - це рефакторинг вашого методу.

Щоразу, коли у мене виникають проблеми з тестуванням методу, для мене це запах коду, і я запитую, чому це важко перевірити. І якщо код важко перевірити, його, мабуть, важко використовувати та підтримувати.

У цьому випадку це тому, що у вас є ланцюжок методів, що йде на кілька рівнів углиб. Можливо, передавати в якості параметрів ctx, cacheEnable та cacheExpire.


Так, але ці поля є з контексту aop під час виконання, важко спростити середовище.
jilen

У JMockit є методи для цього. Ви можете знущатися з полів у своїх об’єктах, імітуючи введення поля AOP. Або ви можете скористатися методами деенкапсуляції, що ініціалізують проватні поля знущаними екземплярами
Костянтин Приблуда,

Ви можете навести приклад @doug, будь ласка?
ScottyBlades

1

Я знайшов 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))

1
думає за вашу відповідь, але я використовую mockito, будь-яким чином.
jilen

1
Зверніть увагу , що JMockit має анотацію спеціально для прикутих викликів: @Cascading. Крім того, у таких випадках ви, мабуть, хочете використовувати NonStrictExpectationsзамість цього Expectations, припускаючи, що виклики знущаних методів не потребують перевірки.
Rogério

Дякую, я пропустив цю анотацію;) Пішов спрощувати свої модульні тести
Костянтин Приблуда
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.