Я не понимаю разницы между Mock, Stub и Spy в тестировании Spock, и учебники, которые я просматривал в Интернете, не объясняют их подробно.
Я не понимаю разницы между Mock, Stub и Spy в тестировании Spock, и учебники, которые я просматривал в Интернете, не объясняют их подробно.
Ответы:
Внимание: в следующих параграфах я собираюсь излишне упростить и, возможно, даже немного фальсифицировать. Для получения более подробной информации см . Веб-сайт Мартина Фаулера .
Мок - это фиктивный класс, заменяющий реальный, возвращающий что-то вроде null или 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, и я не верю, что текущие ответы учитывают это.
На основе документации Spock (примеры настроены, добавлены мои собственные формулировки):
Заглушка: используется для того, чтобы соавторы реагировали на вызовы методов определенным образом. При заглушке метода вам все равно, будет ли метод вызываться и сколько раз; вы просто хотите, чтобы он возвращал какое-то значение или выполнял побочный эффект при каждом вызове.
subscriber.receive(_) >> "ok" // subscriber is a Stub()
Mock: используется для описания взаимодействия между объектом спецификации и его участниками.
def "should send message to subscriber"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello") // subscriber is a Mock()
}
Mock может действовать как Mock и Stub:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()
Шпион: всегда основан на реальном объекте с оригинальными методами, которые делают реальные вещи. Может использоваться как заглушка для изменения возвращаемых значений методов выбора. Может использоваться как Mock для описания взаимодействий.
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 (), это может быть запахом и намеком на неправильный тест или неправильный дизайн тестируемого объекта.
Проще говоря:
Mock: вы издеваетесь над шрифтом, и на лету вы создаете объект. Методы в этом фиктивном объекте возвращают значения возвращаемого типа по умолчанию.
Заглушка: вы создаете класс-заглушку, в котором методы переопределяются с определением в соответствии с вашими требованиями. Пример: в реальном методе объекта вы вызываете и внешний API и возвращаете имя пользователя и идентификатор. В методе заглушенного объекта вы возвращаете фиктивное имя.
Шпион: вы создаете один реальный объект, а затем шпионите за ним. Теперь вы можете издеваться над некоторыми методами и не делать этого для некоторых.
Одно различие в использовании заключается в том, что вы не можете имитировать объекты уровня метода. тогда как вы можете создать объект по умолчанию в методе, а затем следить за ним, чтобы получить желаемое поведение методов в шпионском объекте.
Заглушки предназначены только для облегчения модульного тестирования, они не являются частью теста. Моки - это часть теста, часть проверки, часть пройден / не пройден.
Итак, допустим, у вас есть метод, который принимает объект в качестве параметра. Вы никогда не делаете ничего, что изменяет этот параметр в тесте. Вы просто считываете из него значение. Это заглушка.
Если вы что-то измените или вам нужно проверить какое-то взаимодействие с объектом, то это фикция.