Разница между макетом / заглушкой / шпионом в рамках теста Спока


Я не понимаю разницы между макетом, заглушкой и шпионом в тестировании Спока, и учебники, которые я смотрел в Интернете, не объясняют их подробно.

3 66

3 ответа:

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

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

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

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


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

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

теперь вот исполняемый пример теста, демонстрирующий, что возможно и чего нет. Это немного более поучительно, чем фрагменты Михаила. Большое спасибо ему за то, что вдохновил меня улучшить свой собственный ответ! : -)

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 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()
}

макет может выступать в качестве макета и заглушки:

1 * subscriber.receive("message1") >> "ok" // subscriber is a 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
}

резюме:

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

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

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

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

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

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

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

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