Подтвердить что ты не робот

Может ли кто-нибудь объяснить разницу между Mock, Stub и Spy в тестировании платформы Spock и когда их использовать?

Я не понимаю разницы между Mock, Stub и Spy в тестировании Spock, а обучающие материалы, которые я просматривал в Интернете, не объясняют их подробно.

4b9b3361

Ответ 1

Внимание: я собираюсь упростить и, возможно, даже слегка фальсифицировать в следующих параграфах. Более подробную информацию см. сайт Мартина Фаулера.

Макет - это фиктивный класс, заменяющий реальный, возвращающий что-то вроде нуля или 0 для каждого вызова метода. Вы используете макет, если вам нужен фиктивный экземпляр сложного класса, который в противном случае использовал бы внешние ресурсы, такие как сетевые подключения, файлы или базы данных или, возможно, использовать десятки других объектов. Преимущество mocks заключается в том, что вы можете изолировать тестируемый класс от остальной системы.

Штук также является фиктивным классом, предоставляющим некоторые более конкретные, подготовленные или предварительно записанные повторно воспроизведенные результаты для определенных тестируемых запросов. Вы могли бы сказать, что заглушка - причудливый макет. В Spock вы часто будете читать о методах заглушки.

Шпион - это своего рода гибрид между реальным объектом и заглушкой, т.е. он в основном является реальным объектом с некоторыми (не всеми) методами, затененными методами заглушки. Необработанные методы просто направляются к исходному объекту. Таким образом, вы можете иметь оригинальное поведение для "дешевых" или тривиальных методов и поддельного поведения для "дорогих" или сложных методов.


Обновление 2017-02-06: На самом деле пользовательский ответ mikhail более специфичен для Spock, чем мой оригинальный выше. Итак, в рамках Спока, то, что он описывает, является правильным, но это не фальсифицирует мой общий ответ:

  • Штук касается моделирования конкретного поведения. В Споке это все, что может сделать заглушка, поэтому это своего рода простейшая вещь.
  • Макет связан с постоянным (возможно, дорогостоящим) реальным объектом, предоставляющим ответы без ответа для всех вызовов методов. В этом отношении макет проще, чем заглушка. Но в Споке макет может также привести результаты метода заглушки, т.е. Быть как макетом, так и заглушкой. Кроме того, в Spock мы можем подсчитать, как часто во время теста были вызваны определенные макетные методы с определенными параметрами.
  • Шпион всегда обертывает реальный объект и по умолчанию направляет все вызовы методов на исходный объект, также проходя через исходные результаты. Метод подсчета вызовов также работает для шпионов. В Spock шпион может также изменять поведение исходного объекта, манипулировать параметрами вызова метода и/или результатами или вообще блокировать исходные методы.

Теперь вот пример исполняемого примера, демонстрирующий, что возможно, а что нет. Это немного более поучительно, чем фрагменты 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?")
  }
}

Ответ 2

Вопрос был в контексте структуры Spock, и я не считаю, что в текущих ответах это учитывается.

На основе Spock docs (примеры настроены, добавлена ​​моя собственная формулировка):

Штук: Используется для того, чтобы соавторы отвечали на вызовы методов определенным образом. При выполнении метода вам небезразлично, если и сколько раз метод будет вызван; вы просто хотите, чтобы он возвращал какое-то значение или выполнял какой-то побочный эффект при каждом вызове.

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 может действовать как макет и заглушка:

1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()

Шпион: Всегда основан на реальном объекте с оригинальными методами, которые делают реальные вещи. Может использоваться как Stub для изменения возвращаемых значений методов select. Может использоваться как макет для описания взаимодействий.

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
}

Резюме:

  • A Stub() - это заглушка.
  • Mock() - это заглушка и макет.
  • Шпион() - это заглушка, макет и шпион.

Избегайте использования Mock(), если достаточно Stub().

Избегайте использования Spy(), если это возможно, это может быть запах и намеки на неправильный тест или неправильный дизайн тестируемого объекта.

Ответ 3

Простыми словами:

Mock: вы издеваетесь над типом, и на лету вы получаете созданный объект. Методы в этом макетном объекте возвращают значения возвращаемого значения по умолчанию.

Штук: вы создаете класс заглушки, где методы переопределяются с определением в соответствии с вашим требованием. Пример: в режиме реального объекта вы вызываете и внешние api и возвращаете имя пользователя и id. В методе stubbed object вы возвращаете некоторое фиктивное имя.

Шпион: вы создаете один реальный объект, а затем шпионируете его. Теперь вы можете высмеять некоторые методы и решили не делать этого для некоторых.

Одна разница в использовании, вы не можете имитировать объекты уровня метода. тогда как вы можете создать объект по умолчанию в методе, а затем шпионить за ним, чтобы получить желаемое поведение методов в spied-объекте.