Sự khác biệt giữa Mock / Stub / Spy trong khung thử nghiệm Spock


101

Tôi không hiểu sự khác biệt giữa Mock, Stub và Spy trong thử nghiệm Spock và các hướng dẫn tôi đã xem trên mạng không giải thích chi tiết về chúng.

Câu trả lời:


94

Chú ý: Tôi sẽ đơn giản hóa quá mức và thậm chí có thể hơi sai lệch trong các đoạn văn sắp tới. Để biết thêm thông tin chi tiết, hãy xem trang web của Martin Fowler .

Mô hình là một lớp giả thay thế một lớp thực, trả về một thứ gì đó như null hoặc 0 cho mỗi lần gọi phương thức. Bạn sử dụng một mô hình nếu bạn cần một phiên bản giả của một lớp phức tạp mà nếu không sẽ sử dụng các tài nguyên bên ngoài như kết nối mạng, tệp hoặc cơ sở dữ liệu hoặc có thể sử dụng hàng chục đối tượng khác. Ưu điểm của mocks là bạn có thể cách ly lớp đang kiểm tra với phần còn lại của hệ thống.

Sơ khai cũng là một lớp giả cung cấp một số kết quả cụ thể hơn, được chuẩn bị hoặc ghi trước, phát lại cho các yêu cầu nhất định đang được thử nghiệm. Bạn có thể nói sơ khai là một sự giả tạo ưa thích. Trong Spock, bạn thường sẽ đọc về các phương thức sơ khai.

Một spy là loại lai giữa đối tượng thực và sơ khai, tức là về cơ bản nó là đối tượng thực với một số (không phải tất cả) phương thức bị che bởi các phương thức sơ khai. Các phương thức không có gốc chỉ được chuyển đến đối tượng gốc. Bằng cách này, bạn có thể có hành vi ban đầu cho các phương thức "rẻ" hoặc tầm thường và hành vi giả mạo cho các phương pháp "đắt" hoặc phức tạp.


Cập nhật 2017-02-06: Thực tế câu trả lời của người dùng mikhail cụ thể hơn cho Spock so với câu trả lời ban đầu của tôi ở trên. Vì vậy, trong phạm vi của Spock, những gì anh ấy mô tả là đúng, nhưng điều đó không làm sai lệch câu trả lời chung của tôi:

  • Sơ khai liên quan đến việc mô phỏng hành vi cụ thể. Trong Spock, đây là tất cả những gì một người sơ khai có thể làm, vì vậy nó là một thứ đơn giản nhất.
  • Một mô hình liên quan đến việc đứng trong một đối tượng thực (có thể đắt tiền), cung cấp câu trả lời không cần chọn cho tất cả các lệnh gọi phương thức. Về mặt này, một mô hình đơn giản hơn một sơ khai. Nhưng trong Spock, một mô hình cũng có thể khai báo kết quả của phương pháp, tức là vừa là mô hình vừa là sơ khai. Hơn nữa, trong Spock, chúng ta có thể đếm tần suất các phương thức giả cụ thể với các tham số nhất định đã được gọi trong quá trình kiểm tra.
  • Một gián điệp luôn bao bọc một đối tượng thực và theo mặc định định tuyến tất cả các lệnh gọi phương thức đến đối tượng ban đầu, cũng đi qua các kết quả ban đầu. Phương thức đếm cuộc gọi cũng hoạt động đối với gián điệp. Trong Spock, một gián điệp cũng có thể sửa đổi hành vi của đối tượng ban đầu, thao túng các tham số và / hoặc kết quả gọi phương thức hoặc chặn hoàn toàn không cho gọi các phương thức gốc.

Bây giờ đây là một thử nghiệm ví dụ có thể thực thi, chứng minh điều gì có thể và điều gì không. Nó mang tính hướng dẫn nhiều hơn một chút so với các đoạn trích của mikhail. Rất cám ơn anh ấy đã truyền cảm hứng để tôi cải thiện câu trả lời của chính mình! :-)

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?")
  }
}

Sự khác biệt giữa mô hình và sơ khai không rõ ràng ở đây. Với mocks, người ta muốn xác minh hành vi (nếu và bao nhiêu lần phương thức sẽ được gọi). Với sơ khai, người ta chỉ xác minh trạng thái (ví dụ: kích thước của bộ sưu tập sau khi kiểm tra). FYI: mô phỏng cũng có thể cung cấp kết quả đã chuẩn bị.
chipiik

Cảm ơn @mikhail và chipiik vì phản hồi của bạn. Tôi đã cập nhật câu trả lời của mình, hy vọng sẽ cải thiện và làm rõ một vài điều tôi đã viết ban đầu. Tuyên bố từ chối trách nhiệm: Trong câu trả lời ban đầu của tôi, tôi đã nói rằng tôi đã đơn giản hóa quá mức và hơi sai lệch một số sự kiện liên quan đến Spock. Tôi muốn mọi người hiểu những khác biệt cơ bản giữa việc khai báo, chế giễu và gián điệp.
kriegaex

@chipiik, một điều nữa xin trả lời cho nhận xét của bạn: Tôi đã huấn luyện các nhóm phát triển trong nhiều năm và thấy họ sử dụng Spock hoặc JUnit khác với các khuôn khổ giả khác. Trong hầu hết các trường hợp khi sử dụng mocks, họ không làm điều đó để xác minh hành vi (tức là các cuộc gọi phương thức đếm) mà để cách ly đối tượng được kiểm tra khỏi môi trường của nó. IMO đếm tương tác chỉ là một tiện ích bổ sung và nên được sử dụng một cách thận trọng và tiết kiệm vì có xu hướng các thử nghiệm như vậy bị hỏng khi họ kiểm tra hệ thống dây của các thành phần nhiều hơn hành vi thực tế của chúng.
kriegaex

Ngắn gọn của nó, nhưng vẫn trả lời rất hữu ích
Chaklader Asfak Arefe

55

Câu hỏi đặt ra trong bối cảnh của khung Spock và tôi không tin rằng các câu trả lời hiện tại có tính đến điều này.

Dựa trên tài liệu Spock (ví dụ được tùy chỉnh, từ ngữ của riêng tôi được thêm vào):

Stub: Được sử dụng để làm cho các cộng tác viên trả lời các cuộc gọi phương thức theo một cách nhất định. Khi khai báo một phương thức, bạn không quan tâm phương thức đó sẽ được gọi bao nhiêu lần; bạn chỉ muốn nó trả về một số giá trị hoặc thực hiện một số hiệu ứng phụ, bất cứ khi nào nó được gọi.

subscriber.receive(_) >> "ok" // subscriber is a Stub()

Mock: Được sử dụng để mô tả các tương tác giữa đối tượng được đặc tả và các cộng tác viên của nó.

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("hello") // subscriber is a Mock()
}

Mock có thể hoạt động như một Mock và Stub:

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

Spy: Luôn dựa trên một đối tượng thực với các phương pháp ban đầu làm những việc thực. Có thể được sử dụng như một Stub để thay đổi giá trị trả về của các phương thức được chọn. Có thể được sử dụng như Mock để mô tả các tương tác.

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
}

Tóm lược:

  • Một Stub () là một Stub.
  • Mock () là một Stub và Mock.
  • A Spy () là một Stub, Mock và Spy.

Tránh sử dụng Mock () nếu Stub () là đủ.

Tránh sử dụng Spy () nếu bạn có thể, việc làm như vậy có thể là một mùi và gợi ý về việc kiểm tra không chính xác hoặc thiết kế không chính xác của đối tượng được kiểm tra.


1
Chỉ cần nói thêm: Một lý do khác khiến bạn muốn giảm thiểu việc sử dụng mô phỏng, đó là mô phỏng rất giống với một lời khẳng định, ở chỗ bạn kiểm tra những thứ trên mô hình giả có thể không đạt được bài kiểm tra và bạn luôn muốn giảm thiểu số lần kiểm tra bạn làm trong một bài kiểm tra, để giữ cho bài kiểm tra tập trung và đơn giản. Vì vậy, lý tưởng nhất là chỉ nên có một bài thi thử cho mỗi bài kiểm tra.
Sammi

1
"A Spy () là một Stub, Mock and Spy." điều này không đúng với gián điệp sinon?
K - Độc tính trong SO ngày càng lớn.

2
Tôi chỉ nhìn sơ qua về các điệp viên của Sinon và trông họ có vẻ như không cư xử như Mocks hay Stubs. Lưu ý rằng câu hỏi / câu trả lời này nằm trong ngữ cảnh của Spock, là Groovy, không phải JS.
mikhail

Đây phải là câu trả lời chính xác, vì điều này được áp dụng cho ngữ cảnh Spock. Ngoài ra, việc nói rằng một bản sơ khai là một mô hình lạ mắt có thể gây hiểu lầm, vì một bản mô phỏng có chức năng bổ sung (kiểm tra số lượng lệnh gọi) mà bản gốc không có (mô hình> huyền ảo hơn bản gốc). Một lần nữa, chế nhạo và khai báo theo Spock.
CGK

13

Nói một cách dễ hiểu:

Mock: Bạn mô phỏng một kiểu và ngay lập tức bạn sẽ tạo được một đối tượng. Các phương thức trong đối tượng giả lập này trả về các giá trị mặc định của kiểu trả về.

Stub: Bạn tạo một lớp sơ khai nơi các phương thức được định nghĩa lại với định nghĩa theo yêu cầu của bạn. Ví dụ: Trong phương thức đối tượng thực, bạn gọi và api bên ngoài và trả về tên người dùng đối với và id. Trong phương thức đối tượng gốc, bạn trả về một số tên giả.

Gián điệp: Bạn tạo ra một đối tượng thực và sau đó bạn theo dõi nó. Bây giờ bạn có thể bắt chước một số phương pháp và chọn không làm như vậy đối với một số phương pháp.

Một sự khác biệt khi sử dụng là bạn không thể giả lập các đối tượng mức phương thức. trong khi bạn có thể tạo một đối tượng mặc định trong phương thức và sau đó theo dõi nó để có được hành vi mong muốn của các phương thức trong đối tượng gián điệp.


0

Stubs thực sự chỉ để tạo điều kiện cho bài kiểm tra đơn vị, chúng không phải là một phần của bài kiểm tra. Mocks, là một phần của bài kiểm tra, một phần của xác minh, một phần của việc đạt / không đạt.

Vì vậy, giả sử bạn có một phương thức nhận một đối tượng làm tham số. Bạn không bao giờ làm bất cứ điều gì thay đổi thông số này trong thử nghiệm. Bạn chỉ cần đọc một giá trị từ nó. Đó là một sơ khai.

Nếu bạn thay đổi bất cứ điều gì hoặc cần xác minh một số loại tương tác với đối tượng, thì đó là một mô hình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.