Я не розумію різниці між Mock, Stub та Spy у Spock тестуванні та навчальні посібники, які я переглядав в Інтернеті, не пояснюють їх докладно.
Я не розумію різниці між Mock, Stub та Spy у Spock тестуванні та навчальні посібники, які я переглядав в Інтернеті, не пояснюють їх докладно.
Відповіді:
Увага: я збираюся спростити і, можливо, навіть трохи підробити в наступних пунктах. Більш детальну інформацію див. На веб-сайті Мартіна Фаулера .
Макет - це фіктивний клас, що замінює справжній, повертаючи щось на зразок нуля або 0 для кожного виклику методу. Ви використовуєте макет, якщо вам потрібен фіктивний екземпляр складного класу, який інакше використовує зовнішні ресурси, такі як мережеві з'єднання, файли чи бази даних, або, можливо, використовує десятки інших об'єктів. Перевага макетів полягає в тому, що ви можете ізолювати досліджуваний клас від решти системи.
Заглушка - це також фіктивний клас, який дає деякі більш конкретні, підготовлені або попередньо записані, відтворені результати для певних тестових запитів. Можна сказати, заглушка - це химерний глуз. У Spock ви часто читатимете про методи заглушки.
Шпигун - це різновид гібриду між реальним об'єктом і заглушкою, тобто в основному це реальний об'єкт з деякими (не всіма) методами, затіненими методами заглушки. Несмучені методи просто перенаправляються до оригінального об'єкта. Таким чином, ви можете мати оригінальну поведінку для «дешевих» або тривіальних методів та фальшиву поведінку для «дорогих» або складних методів.
Оновлення 2017-02-06: Насправді відповідь користувача mikhail є більш специфічною для Спока, ніж моя первісна вище. Тож у межах Спока те, що він описує, є правильним, але це не підробляє мою загальну відповідь:
Тепер ось прикладний тест, який демонструє, що можливо, а що ні. Це трохи більш повчально, ніж фрагменти Міхаїла. Велика подяка йому за те, що надихнув мене на покращення власної відповіді! :-)
package de.scrum_master.stackoverflow
import org.spockframework.mock.TooFewInvocationsError
import org.spockframework.runtime.InvalidSpecException
import spock.lang.FailsWith
import spock.lang.Specification
class MockStubSpyTest extends Specification {
static class Publisher {
List<Subscriber> subscribers = new ArrayList<>()
void addSubscriber(Subscriber subscriber) {
subscribers.add(subscriber)
}
void send(String message) {
for (Subscriber subscriber : subscribers)
subscriber.receive(message);
}
}
static interface Subscriber {
String receive(String message)
}
static class MySubscriber implements Subscriber {
@Override
String receive(String message) {
if (message ==~ /[A-Za-z ]+/)
return "ok"
return "uh-oh"
}
}
Subscriber realSubscriber1 = new MySubscriber()
Subscriber realSubscriber2 = new MySubscriber()
Publisher publisher = new Publisher(subscribers: [realSubscriber1, realSubscriber2])
def "Real objects can be tested normally"() {
expect:
realSubscriber1.receive("Hello subscribers") == "ok"
realSubscriber1.receive("Anyone there?") == "uh-oh"
}
@FailsWith(TooFewInvocationsError)
def "Real objects cannot have interactions"() {
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then:
2 * realSubscriber1.receive(_)
}
def "Stubs can simulate behaviour"() {
given:
def stubSubscriber = Stub(Subscriber) {
receive(_) >>> ["hey", "ho"]
}
expect:
stubSubscriber.receive("Hello subscribers") == "hey"
stubSubscriber.receive("Anyone there?") == "ho"
stubSubscriber.receive("What else?") == "ho"
}
@FailsWith(InvalidSpecException)
def "Stubs cannot have interactions"() {
given: "stubbed subscriber registered with publisher"
def stubSubscriber = Stub(Subscriber) {
receive(_) >> "hey"
}
publisher.addSubscriber(stubSubscriber)
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then:
2 * stubSubscriber.receive(_)
}
def "Mocks can simulate behaviour and have interactions"() {
given:
def mockSubscriber = Mock(Subscriber) {
3 * receive(_) >>> ["hey", "ho"]
}
publisher.addSubscriber(mockSubscriber)
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then: "check interactions"
1 * mockSubscriber.receive("Hello subscribers")
1 * mockSubscriber.receive("Anyone there?")
and: "check behaviour exactly 3 times"
mockSubscriber.receive("foo") == "hey"
mockSubscriber.receive("bar") == "ho"
mockSubscriber.receive("zot") == "ho"
}
def "Spies can have interactions"() {
given:
def spySubscriber = Spy(MySubscriber)
publisher.addSubscriber(spySubscriber)
when:
publisher.send("Hello subscribers")
publisher.send("Anyone there?")
then: "check interactions"
1 * spySubscriber.receive("Hello subscribers")
1 * spySubscriber.receive("Anyone there?")
and: "check behaviour for real object (a spy is not a mock!)"
spySubscriber.receive("Hello subscribers") == "ok"
spySubscriber.receive("Anyone there?") == "uh-oh"
}
def "Spies can modify behaviour and have interactions"() {
given:
def spyPublisher = Spy(Publisher) {
send(_) >> { String message -> callRealMethodWithArgs("#" + message) }
}
def mockSubscriber = Mock(MySubscriber)
spyPublisher.addSubscriber(mockSubscriber)
when:
spyPublisher.send("Hello subscribers")
spyPublisher.send("Anyone there?")
then: "check interactions"
1 * mockSubscriber.receive("#Hello subscribers")
1 * mockSubscriber.receive("#Anyone there?")
}
}
Питання було в контексті Спока, і я не вірю, що нинішні відповіді враховують це.
Спираючись на документи Spock (приклади налаштовані, додано моє власне формулювання):
Заглушка: використовується для того, щоб співробітники певним чином відповідали на виклики методів. Під час заглушки методу вам не байдуже, чи і скільки разів метод буде викликаний; ви просто хочете, щоб він повертав деяке значення або виконував якийсь побічний ефект, щоразу, коли його викликають.
subscriber.receive(_) >> "ok" // subscriber is a Stub()
Макет: використовується для опису взаємодій між об'єктом, що знаходиться під специфікацією, та його співавторами.
def "should send message to subscriber"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello") // subscriber is a Mock()
}
Знущання може виступати як глузування та заглушка:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()
Шпигун: завжди базується на реальному об'єкті з оригінальними методами, які роблять реальні речі. Можна використовувати як Stub для зміни повернутих значень вибраних методів. Може використовуватися як Макет для опису взаємодій.
def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])
def "should send message to subscriber"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Spy(), used as a Mock an Stub
}
def "should send message to subscriber (actually handle 'receive')"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("message1") // subscriber is a Spy(), used as a Mock, uses real 'receive' function
}
Підсумок:
Уникайте використання Mock (), якщо Stub () є достатнім.
Уникайте використання Spy (), якщо можете, якщо це зробити, це може бути запахом і натяком на неправильний тест або неправильну конструкцію тестуваного об'єкта.
Простими словами:
Знущання: Ви знущаєтесь над типом і на ходу отримуєте створений об’єкт. Методи в цьому макетному об’єкті повертають типові значення типу повернення.
Стуб: Ви створюєте клас заглушки, де методи переосмислюються із визначенням відповідно до вашої вимоги. Наприклад: у реальному об'єктному методі ви викликаєте і зовнішній api та повертаєте ім'я користувача проти та id. У методі стертого об’єкта ви повертаєте якесь манекенне ім'я.
Шпигун: Ви створюєте один реальний об'єкт, а потім шпигуєте за ним. Тепер ви можете знущатися над деякими методами і вирішувати не робити цього для деяких.
Одна відмінність використання полягає в тому, що ви не можете знущатися над об'єктами рівня методу. тоді як ви можете створити об'єкт за замовчуванням у методі, а потім шпигувати за ним, щоб отримати бажану поведінку методів у шпигуваному об'єкті.
Заглушки дійсно лише для полегшення одиничного тесту, вони не є частиною тесту. Макети, частина тесту, частина перевірки, частина проходження / відмови.
Так, скажімо, у вас є метод, який приймає об’єкт як параметр. Ви ніколи не робите нічого, що змінює цей параметр у тесті. Ви просто читаєте значення з нього. Це заглушка.
Якщо ви щось змінили або вам потрібно перевірити якусь взаємодію з об’єктом, то це - насмішка.