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


101

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

답변:


94

주의 : 나는 다음 단락에서 지나치게 단순화하거나 약간 위조 할 것입니다. 자세한 정보는 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?")
  }
}

mock과 stub의 차이점은 여기서 명확하지 않습니다. mock을 사용하면 동작을 확인하려고합니다 (메서드가 호출되는 경우 및 횟수). 스텁을 사용하면 상태 만 확인합니다 (예 : 테스트 후 수집 크기). 참고 : 모의는 준비된 결과도 제공 할 수 있습니다.
chipiik

의견을 보내 주신 @mikhail과 chipiik에게 감사드립니다. 나는 내 답변을 업데이트하여 내가 원래 작성한 몇 가지 사항을 개선하고 명확히했습니다. 면책 조항 : 원래 답변에서 나는 Spock 관련 사실을 지나치게 단순화하고 약간 위조한다고 말했습니다. 나는 사람들이 스터 빙, 조롱 및 스파이의 기본적인 차이점을 이해하기를 원했습니다.
kriegaex

@chipiik, 귀하의 의견에 대한 답변으로 한 가지 더 : 저는 수년간 개발 팀을 코칭 해 왔으며 다른 모의 프레임 워크와 함께 Spock 또는 다른 JUnit을 사용하는 것을 보았습니다. 대부분의 경우 모의를 사용하는 경우 동작을 확인하기 위해 (즉, 카운트 메서드 호출) 수행하지 않고 테스트 대상을 환경에서 격리합니다. Interaction counting IMO는 추가 기능 일 뿐이며 실제 동작보다 구성 요소의 배선을 테스트 할 때 이러한 테스트가 중단되는 경향이 있기 때문에 신중하고 드물게 사용해야합니다.
kriegaex

그 짧은하지만 여전히 아주 도움이 대답
Chaklader Asfak Arefe

55

질문은 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 ()를 사용하지 마십시오. 그렇게해야 냄새가 날 수 있고 잘못된 테스트에 대한 힌트 나 테스트 대상 개체의 잘못된 디자인을 알 수 있습니다.


1
추가 할 사항 : 모의 사용을 최소화하려는 또 다른 이유는 모의가 어설 션과 매우 유사하기 때문입니다. 테스트에 실패 할 수있는 모의 항목을 확인하고 항상 확인의 양을 최소화하기를 원한다는 점입니다. 테스트를 집중하고 간단하게 유지하기 위해 테스트를 수행합니다. 따라서 이상적으로는 테스트 당 모의가 하나만 있어야합니다.
Sammi

1
"Spy ()는 스텁, 모의 및 스파이입니다." 이것은 sinon 스파이에게 사실이 아닙니까?
K-SO의 독성이 증가하고 있습니다.

2
방금 Sinon 스파이를 간략히 살펴 보았는데 그들은 Mocks 또는 Stubs처럼 행동하지 않는 것처럼 보입니다. 이 질문 / 답변은 JS가 아닌 Groovy 인 Spock의 맥락에 있습니다.
mikhail

이것은 Spock 컨텍스트로 범위가 지정되므로 정답이어야합니다. 또한 스텁이 멋진 모의라고 말하는 것은 오해의 소지가있을 수 있습니다. 모의에는 스텁이없는 추가 기능 (호출 횟수 확인)이 있기 때문입니다. 다시, Spock에 따라 모의와 스텁.
CGK

13

간단히 말해서 :

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

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

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

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


0

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.