[testing] Spock 테스트 프레임 워크에서 Mock / Stub / Spy의 차이점

Spock 테스트에서 Mock, Stub 및 Spy의 차이점을 이해하지 못하며 온라인에서 살펴본 자습서에서 자세히 설명하지 않습니다.



답변

주의 : 나는 다음 단락에서 지나치게 단순화하거나 약간 위조 할 것입니다. 자세한 정보는 Martin Fowler의 웹 사이트를 참조하십시오 .

mock은 실제 클래스를 대체하는 더미 클래스로, 각 메서드 호출에 대해 null 또는 0과 같은 것을 반환합니다. 네트워크 연결, 파일 또는 데이터베이스와 같은 외부 리소스를 사용하거나 수십 개의 다른 개체를 사용하는 복잡한 클래스의 더미 인스턴스가 필요한 경우 모의를 사용합니다. mock의 장점은 테스트중인 클래스를 나머지 시스템과 분리 할 수 ​​있다는 것입니다.

스텁은 또한 테스트중인 특정 요청에 대해 좀 더 구체적이고 준비되거나 미리 녹음 된 재생 결과를 제공하는 더미 클래스입니다. 스텁이 멋진 모의라고 말할 수 있습니다. Spock에서는 스텁 메소드에 대해 자주 읽습니다.

스파이는 실제 객체와 스텁 사이의 일종의 하이브리드입니다. 즉, 기본적으로 일부 (전부는 아님) 메소드가 스텁 메소드에 의해 음영 처리 된 실제 객체입니다. 스텁되지 않은 메서드는 원래 개체로 라우팅됩니다. 이렇게하면 “저렴한”또는 사소한 방법에 대한 원래 동작과 “비싸거나”복잡한 방법에 대한 가짜 동작을 가질 수 있습니다.


업데이트 2017-02-06 : 실제로 사용자 mikhail의 답변은 위의 원래 답변보다 Spock에 더 구체적입니다. 따라서 Spock의 범위 내에서 그가 설명하는 것은 정확하지만 내 일반적인 대답을 위조하지는 않습니다.

  • 스텁은 특정 동작을 시뮬레이션하는 것과 관련이 있습니다. Spock에서 이것은 스텁이 할 수있는 모든 것이므로 가장 간단한 것입니다.
  • 모의 객체는 (비싼) 실제 객체를 위해 대기하여 모든 메서드 호출에 대해 무 운영 응답을 제공하는 것과 관련이 있습니다. 이와 관련하여 모의는 스텁보다 간단합니다. 그러나 Spock에서 mock은 메서드 결과를 스텁 할 수도 있습니다. 즉, mock과 stub이 될 수 있습니다. 또한 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?")
  }
}


답변

질문은 Spock 프레임 워크의 맥락에 있었으며 현재 답변이 이것을 고려한다고 생각하지 않습니다.

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

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
}

요약:

  • Stub ()은 Stub입니다.
  • Mock ()은 Stub 및 Mock입니다.
  • Spy ()는 Stub, Mock 및 Spy입니다.

Stub ()이 충분하면 Mock ()을 사용하지 마십시오.

가능하다면 Spy ()를 사용하지 마십시오. 그렇게해야 냄새가 날 수 있고 잘못된 테스트에 대한 힌트 나 테스트 대상 개체의 잘못된 디자인을 알 수 있습니다.


답변

간단히 말해서 :

모의 : 유형을 조롱하고 즉석에서 객체를 생성합니다. 이 모의 객체의 메서드는 반환 유형의 기본값을 반환합니다.

스텁 : 요구 사항에 따라 정의로 메서드가 재정의되는 스텁 클래스를 만듭니다. 예 : 실제 개체 메서드에서 외부 API를 호출하고 및 ID에 대한 사용자 이름을 반환합니다. 스텁 객체 메서드에서 더미 이름을 반환합니다.

스파이 : 하나의 실제 개체를 만든 다음 스파이합니다. 이제 몇 가지 방법을 모의 할 수 있고 일부에 대해서는 그렇게하지 않도록 선택할 수 있습니다.

한 가지 사용 차이점은 메서드 수준 개체를 모의 할 수 없다는 것입니다. 메서드에서 기본 개체를 만든 다음 스파이 대상 개체에서 원하는 메서드 동작을 얻을 수 있습니다.


답변

스텁은 실제로 단위 테스트를 용이하게하기위한 것일뿐 테스트의 일부가 아닙니다. 모의는 테스트의 일부, 검증의 일부, 합격 / 불합격의 일부입니다.

따라서 객체를 매개 변수로받는 메소드가 있다고 가정 해 보겠습니다. 테스트에서이 매개 변수를 변경하는 작업은 절대 수행하지 않습니다. 당신은 단순히 그것에서 값을 읽습니다. 스텁입니다.

무언가를 변경하거나 개체와의 상호 작용을 확인해야하는 경우 모의입니다.


답변