Mockito - @Spy vs @Mock


102

Mockito - я розумію, шпигун викликає реальні методи на об'єкті, тоді як макет викликає методи на подвійному об'єкті. Також слід уникати шпигунів, якщо немає запаху коду. Однак як працюють шпигуни і коли я насправді повинен ними користуватися? Чим вони відрізняються від насмішок?



Відповіді:


91

Технічно кажучи, і "знущання", і "шпигуни" є особливим видом "тестових дублів".

На жаль, Mockito робить різницю дивною.

Макет в mockito - це звичайний макет в інших фреймворках для насмішок (дозволяє заглушити виклики; тобто повернути конкретні значення з викликів методів).

Шпигун в mockito - це частковий знущання в інших фреймворках для насмішок (частина об’єкта буде знущатися, а частина використовуватиме реальні виклики методів).


43

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

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

Розглянемо приклад нижче як порівняння.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

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


Хороша відповідь, але це призведе до перевірки () на помилкову помилку і не буде запускати тести, якщо ви не ініціалізуєте свої списки методом @Before setUp (), як тут mockList = mock (ArrayList.class); spyList = шпигун (ArrayList.class); і видаліть запропоновану тут анотацію маніпуляцій і шпигунства. Я тестував його, і мої тести зараз проходять.
The_Martian

17

TL; версія DR,

За допомогою макету він створює для вас екземпляр оголеної оболонки.

List<String> mockList = Mockito.mock(ArrayList.class);

За допомогою шпигуна ви можете частково знущатися над існуючим екземпляром

List<String> spyList = Mockito.spy(new ArrayList<String>());

Типовий варіант використання для Spy: у класі є параметризований конструктор, спочатку потрібно створити об’єкт.


14

Я створив зручний приклад тут 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);
}

Можливо, вам не потрібен шпигун, оскільки ви можете просто знущатися над DepositMoneyService та 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();

Детальніше у посиланні вище.


13

Найкраще місце для початку - це, мабуть, документи для mockito .

Загалом, макет макетів дозволяє створювати заглушки.

Ви б створили метод заглушки, якщо, наприклад, цей метод виконує дорогу операцію. Скажімо, він отримує підключення до бази даних, отримує значення з бази даних і повертає його абоненту. Отримання db-з'єднання може зайняти 30 секунд, сповільнюючи виконання тесту до точки, де ви, швидше за все, переключите контекст (або припините запуск тесту).

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

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

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


7

Коротко:

@Spyі @Mockшироко використовуються при тестуванні коду, але розробники плутають у випадках, коли використовувати один з них, і, отже, розробники в кінцевому підсумку використовують @Mockдля безпеки.

  • Використовуйте, @Mockколи хочете просто перевірити функціональність зовні фактично не викликаючи цей метод.
  • Використовуйте, @Spyколи ви хочете перевірити функціональність зовні + внутрішньо за допомогою самого методу, що викликається.

Нижче наведено приклад, коли я взяв сценарій Election20xx в Америці.

Виборців можна розділити за VotersOfBelow21і VotersOfABove21.

Ідеальний вихід опитування говорить , що Trump виграє вибори , тому що VotersOfBelow21і VotersOfABove21обидва будуть голосувати за козирем кажучи : « Ми вибрали президента Трампа »

Але це не справжній сценарій:

Виборці обох вікових груп проголосували за Трампа, оскільки у них не було іншого ефективного вибору, крім пана Трампа.

То як це перевірити ??

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

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

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

Якби ми тестували ElectionOfYear20XX тестували допомогою @Mock, то, можливо, ми не змогли б зрозуміти справжню причину, чому Трамп переміг, ми просто перевіримо зовнішню причину.

Якщо ми перевіримо на ElectionOfYear20XX@Spy, то ми отримаємо справжню причину, чому Трамп переміг із результатами зовнішнього екзит-полу, тобто внутрішньо + зовні.


Наш ELectionOfYear20XX_Testклас:

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Це повинно виводити лише результати логічного тесту, тобто зовнішню перевірку:

We elected President Trump 
We elected President Trump 

Тестування за допомогою @Spyзовнішнього, а також внутрішнього виклику фактичного методу.

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Вихід:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 

6

Мені подобається простота цієї рекомендації:

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

Джерело: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Загальною відмінністю є:

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

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