Яка різниця між глузуванням та шпигунством під час використання Mockito?


137

Що може бути випадком використання шпигуна Mockito?

Мені здається, що кожен випадок використання шпигуна може оброблятися макетом, використовуючи callRealMethod.

Я бачу одну різницю, якщо ви хочете, щоб більшість викликів методів були справжніми, це зберігає деякі рядки коду для використання макету проти шпигуна. Це це чи я пропускаю більшу картину?

Відповіді:


100

Відповідь - у документації :

Справжні часткові макети (з 1.8.0)

Нарешті, після багатьох внутрішніх дебатів та дискусій у списку розсилки, Mockito була додана часткова підтримка макету. Раніше ми розглядали часткові макети як запахи коду. Однак ми знайшли законний випадок використання для часткових знущань.

Перед випуском 1.8 шпигун () не виробляв справжніх часткових знущань, а для деяких користувачів це бентежить. Докладніше про шпигунство читайте тут або в методі javadoc for spy (Object).

callRealMethod()було введено після spy(), але, звичайно, було залишено шпигуна (), щоб забезпечити зворотну сумісність.

В іншому випадку ви маєте рацію: всі методи шпигуна справжні, якщо їх не обробляють. Усі способи знущань заглушені, якщо callRealMethod()не викликаються. Взагалі, я вважаю за краще використовувати callRealMethod(), бо це не змушує мене використовувати doXxx().when()ідіому замість традиційноїwhen().thenXxx()


Проблема віддати перевагу макету над шпигуном у цих випадках полягає в тому, коли клас використовує член, який не вводиться до нього (але локально ініціалізується) і пізніше використовується «реальним» методом; у макеті член буде ініціалізований до свого значення Java за замовчуванням, що може спричинити неправильну поведінку або навіть NullPointerException. Шлях для цього - додати метод "init", а потім "по-справжньому" викликати його, але це здається мені трохи перебільшеним.
Ейал Рот

Від doc: "Шпигунів слід використовувати обережно та епізодично, наприклад, при роботі зі застарілим кодом." Простір тестування блоку страждає від занадто багато способів зробити те саме.
gdbj

89

Різниця між шпигуном та знущанням

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

У наступному прикладі - ми створюємо макет класу ArrayList:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Як бачите - додавання елемента до списку знущань насправді нічого не додає - воно просто викликає метод без інших побічних ефектів. З іншого боку, шпигун поводитиметься інакше - він фактично викликатиме реальну реалізацію методу add та додасть елемент до нижнього списку:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Тут ми, безумовно, можемо сказати, що справжній внутрішній метод об’єкта називався тому, що при виклику методу size () ви отримуєте розмір як 1, але цей метод size () не знущався! То звідки береться 1? Метод внутрішнього реального розміру () називається тим, що розмір () не знущається (або стрибується), і тому ми можемо сказати, що запис було додано до реального об'єкта.

Джерело: http://www.baeldung.com/mockito-spy + нотатки.


1
Ви не маєте на увазі розмір () повертає 1?
чорний

У першому прикладі, чому mockedList.size()повертається, 0якщо цей метод також не був відмовлений? Це просто значення за замовчуванням, враховуючи тип повернення методу?
мій

@mike: mockedList.size()повертає в Java intзначення за замовчуванням int0. Якщо ви спробуєте виконати assertEquals(0, mockedList.size());після mockedList.clear();, результат залишається однаковим.
realPK

2
Ця відповідь добре і просто написана і допомогла мені нарешті зрозуміти різницю між глузуванням і шпигуном. Хороший.
PesaThe

38

Якщо є об'єкт з 8 методів, і у вас є тест, де ви хочете викликати 7 реальних методів і заглушити один метод, у вас є два варіанти:

  1. Використовуючи макет, вам доведеться налаштувати його, застосувавши 7 callRealMethod та закрепивши один метод
  2. Використовуючи його, spyви повинні налаштувати його за допомогою підключення одного методу

Офіційна документація по doCallRealMethodрекомендує використовувати шпигун для частічнога знущається.

Дивіться також javadoc spy (Object), щоб дізнатися більше про часткові знущання. Mockito.spy () - рекомендований спосіб створення часткових макетів. Причина полягає в тому, що гарантії, що проти правильно побудованого об'єкта викликаються реальні методи, оскільки ви відповідальні за побудову об'єкта, переданого методу spy ()


5

Шпигун може бути корисним, коли ви хочете створити одиничні тести для застарілого коду .

Я створив примірний приклад тут https://www.surasint.com/mockito-with-spy/ , деякі з них копіюю тут.

Якщо у вас є щось подібне до цього коду:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Можливо, вам не потрібен шпигун, оскільки ви можете просто знущатися над MoneyMoneyService та WithdrawMoneyService.

Але з деяким застарілим кодом залежність знаходиться в такому коді:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Так, ви можете змінити перший код, але потім змінити API. Якщо цей метод використовується в багатьох місцях, вам доведеться їх змінити.

Альтернативою є те, що ви можете витягти залежність так:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Тоді ви можете використовувати шпигуна, який вводить залежність так:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

Більш детально за посиланням вище.


0

Mock- голий подвійний предмет. Цей об'єкт має ті ж методи підписів, але реалізація порожня, а повернення за замовчуванням - 0 та null

Spyє клонованим подвійним об'єктом. Новий об’єкт клонується на основі реального об'єкта, але у вас є можливість знущатися над ним

class A {

    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() {
        foo4();
    }

    void foo4() {

    }
}
@Test
public void testMockA() {

    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

[Тест подвійних типів]

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