Mục đích của các đối tượng giả là gì?


167

Tôi chưa quen với thử nghiệm đơn vị và tôi liên tục nghe thấy những từ 'đối tượng giả' được ném xung quanh rất nhiều. Theo thuật ngữ của giáo dân, ai đó có thể giải thích các đối tượng giả là gì và chúng thường được sử dụng để viết bài kiểm tra đơn vị không?


12
Chúng là một công cụ để áp đảo ồ ạt mọi thứ với sự linh hoạt mà bạn không cần phải giải quyết vấn đề.
dsimcha

2
trùng lặp có thể của Mocking là gì?
nawfal

Câu trả lời:


359

Vì bạn nói rằng bạn chưa quen với thử nghiệm đơn vị và đã yêu cầu các đối tượng giả trong "điều khoản của giáo dân", tôi sẽ thử ví dụ của giáo dân.

Kiểm tra đơn vị

Hãy tưởng tượng thử nghiệm đơn vị cho hệ thống này:

cook <- waiter <- customer

Nhìn chung, thật dễ dàng để hình dung thử nghiệm một thành phần cấp thấp như cook:

cook <- test driver

Trình điều khiển thử nghiệm chỉ cần đặt các món ăn khác nhau và xác minh đầu bếp trả lại món ăn chính xác cho mỗi đơn hàng.

Khó hơn để kiểm tra một thành phần trung gian, như người phục vụ, sử dụng hành vi của các thành phần khác. Một người kiểm tra ngây thơ có thể kiểm tra thành phần người phục vụ giống như cách chúng tôi đã kiểm tra thành phần nấu:

cook <- waiter <- test driver

Người lái thử sẽ gọi các món khác nhau và đảm bảo người phục vụ trả lại đúng món. Thật không may, điều đó có nghĩa là thử nghiệm này của thành phần bồi bàn có thể phụ thuộc vào hành vi chính xác của thành phần đầu bếp. Sự phụ thuộc này thậm chí còn tồi tệ hơn nếu thành phần đầu bếp có bất kỳ đặc điểm không thân thiện với thử nghiệm nào, như hành vi không xác định (thực đơn bao gồm sự ngạc nhiên của đầu bếp như một món ăn), rất nhiều sự phụ thuộc (đầu bếp sẽ không nấu mà không có toàn bộ nhân viên của anh ấy), hoặc rất nhiều tài nguyên (một số món ăn yêu cầu nguyên liệu đắt tiền hoặc mất một giờ để nấu ăn).

Vì đây là một bài kiểm tra bồi bàn, lý tưởng nhất, chúng tôi muốn kiểm tra chỉ người phục vụ, không phải người nấu ăn. Cụ thể, chúng tôi muốn đảm bảo người phục vụ truyền đạt yêu cầu của khách hàng đến đầu bếp một cách chính xác và giao thức ăn của đầu bếp cho khách hàng một cách chính xác.

Kiểm thử đơn vị có nghĩa là các đơn vị kiểm tra độc lập, do đó, cách tiếp cận tốt hơn là cách ly thành phần được kiểm tra (người phục vụ) bằng cách sử dụng cái mà Fowler gọi là kiểm tra nhân đôi (giả, cuống, giả, giả) .

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Ở đây, đầu bếp thử nghiệm là "trong cahoots" với trình điều khiển thử nghiệm. Lý tưởng nhất là hệ thống được thử nghiệm được thiết kế sao cho đầu bếp thử nghiệm có thể dễ dàng thay thế ( được tiêm ) để làm việc với người phục vụ mà không thay đổi mã sản xuất (ví dụ: không thay đổi mã bồi bàn).

Đối tượng giả

Bây giờ, đầu bếp thử nghiệm (thử nghiệm kép) có thể được thực hiện theo nhiều cách khác nhau:

  • một đầu bếp giả - một người giả vờ là một đầu bếp bằng cách sử dụng bữa tối đông lạnh và lò vi sóng,
  • một đầu bếp còn sơ khai - một nhà cung cấp xúc xích luôn cung cấp cho bạn những con chó nóng bất kể bạn gọi món gì, hay
  • một đầu bếp giả - một cảnh sát chìm theo kịch bản giả vờ là một đầu bếp trong một hoạt động chích.

Xem bài viết của Fowler để biết thêm chi tiết cụ thể về hàng giả và cuống so với giả và người giả , nhưng bây giờ, hãy tập trung vào một đầu bếp giả.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

Một phần lớn của đơn vị kiểm tra thành phần người phục vụ tập trung vào cách người phục vụ tương tác với thành phần nấu. Một cách tiếp cận dựa trên giả tập trung vào việc xác định đầy đủ những gì tương tác chính xác và phát hiện khi nó đi sai.

Đối tượng giả biết trước những gì sẽ xảy ra trong quá trình thử nghiệm (ví dụ: các cuộc gọi phương thức nào sẽ được gọi, v.v.) và đối tượng giả biết cách phản ứng của nó (ví dụ giá trị trả về sẽ cung cấp). Mock sẽ cho biết liệu những gì thực sự xảy ra khác với những gì được cho là xảy ra. Một đối tượng giả tùy chỉnh có thể được tạo từ đầu cho từng trường hợp thử nghiệm để thực hiện hành vi dự kiến ​​cho trường hợp thử nghiệm đó, nhưng khung mô phỏng cố gắng cho phép một đặc tả hành vi như vậy được chỉ định rõ ràng và dễ dàng trực tiếp trong trường hợp thử nghiệm.

Cuộc trò chuyện xung quanh một bài kiểm tra dựa trên giả có thể như thế này:

kiểm tra trình điều khiển để chế nhạo nấu ăn : mong đợi một đơn đặt hàng hot dog và cung cấp cho anh ta con chó nóng giả này để đáp ứng

lái thử (giả làm khách hàng) để bồi bàn : Tôi muốn một con chó nóng xin
bồi bàn để nhạo báng nấu : 1 hot dog lòng
bếp giả để bồi bàn : Để lên: 1 hot dog sẵn sàng (cho hot dog giả để bồi bàn)
bồi bàn để lái thử : đây là hot dog của bạn (đưa hot dog giả để kiểm tra trình điều khiển)

lái thử : KIỂM TRA THÀNH CÔNG!

Nhưng vì người phục vụ của chúng tôi là người mới, đây là điều có thể xảy ra:

kiểm tra trình điều khiển để chế nhạo nấu ăn : mong đợi một đơn đặt hàng hot dog và cung cấp cho anh ta con chó nóng giả này để đáp ứng

lái xe thử nghiệm (đóng giả là khách hàng) cho người phục vụ : Tôi muốn một con chó nóng xin vui lòng
phục vụ để chế giễu nấu ăn : 1 hamburger xin vui lòng
mock cook dừng thử nghiệm: Tôi đã nói để mong đợi một đơn đặt hàng xúc xích!

trình điều khiển kiểm tra lưu ý vấn đề: KIỂM TRA FAILED! - người phục vụ thay đổi thứ tự

hoặc là

kiểm tra trình điều khiển để chế nhạo nấu ăn : mong đợi một đơn đặt hàng hot dog và cung cấp cho anh ta con chó nóng giả này để đáp ứng

lái thử (giả làm khách hàng) để bồi bàn : Tôi muốn một con chó nóng xin
bồi bàn để nhạo báng nấu : 1 hot dog lòng
bếp giả để bồi bàn : Để lên: 1 hot dog sẵn sàng (cho hot dog giả để bồi bàn)
bồi bàn để lái thử : đây là khoai tây chiên của bạn (cung cấp khoai tây chiên từ một số thứ tự khác để kiểm tra trình điều khiển)

lái xe kiểm tra ghi chú khoai tây chiên bất ngờ: TEST FAILED! Người phục vụ đã trả lại món ăn sai

Có thể khó thấy rõ sự khác biệt giữa các đối tượng giả và sơ khai mà không có ví dụ dựa trên gốc tương phản để đi với điều này, nhưng câu trả lời này đã quá lâu rồi :-)

Cũng lưu ý rằng đây là một ví dụ khá đơn giản và các khung mô phỏng cho phép một số thông số kỹ thuật khá phức tạp về hành vi dự kiến ​​từ các thành phần để hỗ trợ các bài kiểm tra toàn diện. Có rất nhiều tài liệu về các đối tượng giả và khung mô phỏng để biết thêm thông tin.


12
Đây là một lời giải thích tuyệt vời, nhưng bạn không kiểm tra việc thực hiện bồi bàn ở một mức độ nào đó? Trong trường hợp của bạn, có lẽ không sao vì bạn đang kiểm tra xem nó có sử dụng API chính xác không, nhưng nếu có nhiều cách khác nhau để làm điều đó và người phục vụ có thể chọn cái này hay cái khác? Tôi nghĩ rằng mục đích của kiểm thử đơn vị là kiểm tra API chứ không phải triển khai. (Đây là một câu hỏi tôi luôn thấy mình hỏi khi đọc về chế giễu.)
davidtbernal

8
Cảm ơn. Tôi không thể nói liệu chúng tôi đang thử nghiệm "triển khai" mà không cần xem (hoặc xác định) thông số kỹ thuật cho người phục vụ. Bạn có thể cho rằng người phục vụ được phép tự nấu món ăn hoặc điền đơn đặt hàng xuống phố, nhưng tôi cho rằng thông số kỹ thuật cho người phục vụ bao gồm sử dụng đầu bếp dự định - sau tất cả, đầu bếp sản xuất là một đầu bếp sành ăn, đắt tiền và chúng tôi ' d thích người phục vụ của chúng tôi sử dụng anh ta. Nếu không có thông số kỹ thuật đó, tôi đoán tôi phải kết luận bạn đúng - người phục vụ có thể điền vào đơn hàng theo cách họ muốn để "chính xác". OTOH, không có thông số kỹ thuật, bài kiểm tra là vô nghĩa. [Tiếp tục ...]
Bert F

8
KHÔNG BAO GIỜ, bạn thực hiện một điểm tuyệt vời dẫn đến chủ đề tuyệt vời của thử nghiệm đơn vị hộp trắng và hộp đen. Tôi không nghĩ có sự đồng thuận trong ngành nói rằng thử nghiệm đơn vị phải là hộp đen thay vì hộp trắng ("kiểm tra API, không phải thực hiện"). Tôi nghĩ rằng thử nghiệm đơn vị tốt nhất có thể cần phải là sự kết hợp của cả hai để cân bằng độ giòn của thử nghiệm so với độ bao phủ của mã và tính hoàn chỉnh của trường hợp thử nghiệm.
Bert F

1
Câu trả lời này là theo tôi không đủ kỹ thuật. Tôi muốn biết tại sao tôi nên sử dụng đối tượng giả khi tôi có thể sử dụng các đối tượng thực.
Niklas R.

1
Giải thích tuyệt vời !! Cảm ơn bạn!! @BertF
Bharath Murali

28

Đối tượng Mock là một đối tượng thay thế cho một đối tượng thực sự. Trong lập trình hướng đối tượng, các đối tượng giả là các đối tượng mô phỏng bắt chước hành vi của các đối tượng thực theo các cách được kiểm soát.

Một lập trình viên máy tính thường tạo ra một đối tượng giả để kiểm tra hành vi của một số đối tượng khác, giống như cách mà một nhà thiết kế xe hơi sử dụng một hình nộm thử nghiệm va chạm để mô phỏng hành vi động của con người trong các tác động của xe.

http://en.wikipedia.org/wiki/Mock_object

Các đối tượng giả cho phép bạn thiết lập các kịch bản thử nghiệm mà không mang đến các tài nguyên lớn, khó sử dụng như cơ sở dữ liệu. Thay vì gọi một cơ sở dữ liệu để kiểm tra, bạn có thể mô phỏng cơ sở dữ liệu của mình bằng cách sử dụng một đối tượng giả trong các bài kiểm tra đơn vị của bạn. Điều này giải phóng bạn khỏi gánh nặng phải thiết lập và phá bỏ một cơ sở dữ liệu thực sự, chỉ để kiểm tra một phương thức duy nhất trong lớp của bạn.

Từ "Mock" đôi khi được sử dụng nhầm lẫn với "Stub." Sự khác biệt giữa hai từ được mô tả ở đây. Về cơ bản, một giả là một đối tượng còn sơ khai cũng bao gồm các kỳ vọng (nghĩa là "các xác nhận") cho hành vi đúng của đối tượng / phương thức được thử nghiệm.

Ví dụ:

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Lưu ý rằng các đối tượng warehousemailergiả được lập trình với kết quả mong đợi.


2
Định nghĩa bạn đưa ra khác với ít nhất là "đối tượng còn sơ khai" và như vậy không giải thích được đối tượng giả là gì.
Brent Arias

Một cách hiệu chỉnh khác "từ 'Mock' đôi khi được sử dụng nhầm lẫn với 'stub'".
Brent Arias

@Myst: Việc sử dụng hai từ không phổ biến; nó khác nhau giữa các tác giả. Fowler nói như vậy, và bài viết Wikipedia nói như vậy. Tuy nhiên, vui lòng chỉnh sửa trong thay đổi và xóa downvote của bạn. :)
Robert Harvey

1
Tôi đồng ý với Robert: việc sử dụng từ "giả" có xu hướng khác nhau trong toàn ngành, nhưng không có định nghĩa nào được đặt theo kinh nghiệm của tôi ngoại trừ việc nó thường KHÔNG phải là đối tượng thực sự đang được thử nghiệm, thay vào đó nó tồn tại để hỗ trợ kiểm tra khi sử dụng thực tế đối tượng hoặc tất cả các phần của nó sẽ rất bất tiện và ít hậu quả.
mkelley33

15

Các đối tượng giả là các đối tượng mô phỏng bắt chước hành vi của người thật. Thông thường bạn viết một đối tượng giả nếu:

  • Đối tượng thực quá phức tạp để kết hợp nó trong thử nghiệm đơn vị (Ví dụ: giao tiếp qua mạng, bạn có thể có một đối tượng giả mô phỏng là đối tượng ngang hàng khác)
  • Kết quả của đối tượng của bạn là không xác định
  • Đối tượng thực sự chưa có sẵn

12

Đối tượng Mock là một loại Test Double . Bạn đang sử dụng mockobject để kiểm tra và xác minh giao thức / tương tác của lớp được kiểm tra với các lớp khác.

Thông thường, bạn sẽ loại kỳ vọng 'chương trình' hoặc 'bản ghi': các cuộc gọi phương thức mà bạn mong đợi lớp của mình thực hiện với một đối tượng cơ bản.

Ví dụ, giả sử chúng tôi đang thử nghiệm một phương thức dịch vụ để cập nhật một trường trong Widget. Và trong kiến ​​trúc của bạn có một WidgetDAO liên quan đến cơ sở dữ liệu. Nói chuyện với cơ sở dữ liệu chậm và thiết lập và dọn dẹp sau đó rất phức tạp, vì vậy chúng tôi sẽ giả lập WidgetDao.

hãy nghĩ rằng dịch vụ phải làm gì: nó sẽ nhận được Widget từ cơ sở dữ liệu, làm gì đó với nó và lưu lại.

Vì vậy, trong ngôn ngữ giả với thư viện giả, chúng ta sẽ có một cái gì đó như:

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

Theo cách này, chúng ta có thể dễ dàng kiểm tra sự phát triển của các lớp phụ thuộc vào các lớp khác.


11

Tôi đặc biệt giới thiệu một bài viết tuyệt vời của Martin Fowler giải thích chính xác giả là gì và chúng khác với sơ khai như thế nào.


10
Không chính xác thân thiện với người mới bắt đầu, phải không?
Robert Harvey

@Robert Harvey: Có lẽ, dù sao cũng tốt khi thấy rằng nó hữu ích trong việc làm rõ câu trả lời của bạn :)
Adam Byrtek

Các bài báo của Martin Fowler được viết theo cách của RFC: khô và lạnh.
revo

9

Khi đơn vị kiểm tra một phần của chương trình máy tính, lý tưởng nhất là bạn chỉ muốn kiểm tra hành vi của phần cụ thể đó.

Ví dụ: xem mã giả dưới đây từ một đoạn tưởng tượng của chương trình sử dụng chương trình khác để gọi in một cái gì đó:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Nếu bạn đang thử nghiệm điều này, bạn chủ yếu muốn kiểm tra phần xem người dùng có phải là Fred hay không. Bạn không thực sự muốn kiểm tra Printermột phần của sự vật. Đó sẽ là một thử nghiệm khác.

Đây là nơi các đối tượng Mock đến. Họ giả vờ là những loại khác. Trong trường hợp này, bạn sẽ sử dụng Mock Printerđể nó hoạt động giống như một máy in thực sự, nhưng sẽ không làm những điều bất tiện như in ấn.


Có một số loại đối tượng giả vờ khác mà bạn có thể sử dụng không phải là Mocks. Điều chính làm cho Mocks Mocks là chúng có thể được cấu hình với các hành vi và kỳ vọng.

Kỳ vọng cho phép Mock của bạn phát sinh lỗi khi nó được sử dụng không chính xác. Vì vậy, trong ví dụ trên, bạn có thể muốn chắc chắn rằng Máy in được gọi bằng HelloFred trong trường hợp thử nghiệm "người dùng là Fred". Nếu điều đó không xảy ra, Mock của bạn có thể cảnh báo bạn.

Hành vi trong Mocks có nghĩa là ví dụ, mã của bạn đã làm một cái gì đó như:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Bây giờ bạn muốn kiểm tra mã của mình làm gì khi Máy in được gọi và trả về SaidHello, vì vậy bạn có thể thiết lập Mock để trả về SaidHello khi được gọi bằng HelloFred.

Một tài nguyên tốt xung quanh vấn đề này là Martin Fowlers đăng Mocks Ar't Stub


7

Các đối tượng giả và sơ khai là một phần quan trọng của thử nghiệm đơn vị. Trong thực tế, họ đi một chặng đường dài để đảm bảo bạn đang kiểm tra các đơn vị , thay vì các nhóm đơn vị.

Tóm lại, bạn sử dụng sơ khai để phá vỡ sự phụ thuộc của SUT (System Under Test) vào các đối tượng và giả định khác để làm điều đó xác minh rằng SUT gọi các phương thức / thuộc tính nhất định vào phụ thuộc. Điều này quay trở lại các nguyên tắc cơ bản của kiểm thử đơn vị - rằng các bài kiểm tra phải dễ đọc, nhanh chóng và không yêu cầu cấu hình, sử dụng tất cả các lớp thực có thể ngụ ý.

Nói chung, bạn có thể có nhiều hơn một sơ khai trong bài kiểm tra của mình, nhưng bạn chỉ nên có một bản giả. Điều này là do mục đích của giả là để xác minh hành vi và bài kiểm tra của bạn chỉ nên kiểm tra một điều.

Kịch bản đơn giản sử dụng C # và Moq:

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

Trong ví dụ trên tôi đã sử dụng Moq để chứng minh sơ khai và giả. Moq sử dụng cùng một lớp cho cả hai - Mock<T>điều này làm cho nó hơi khó hiểu. Bất kể, trong thời gian chạy, kiểm tra sẽ thất bại nếu output.Writekhông được gọi với dữ liệu parameter, trong khi đó, việc không gọi input.Read()sẽ không thất bại.


4

Như một câu trả lời khác được đề xuất thông qua liên kết đến " Mocks Ar't Stub là một dạng "kiểm tra kép" để sử dụng thay cho một đối tượng thực sự. Điều làm cho chúng khác biệt so với các hình thức kiểm tra nhân đôi khác, chẳng hạn như các đối tượng còn sơ khai, là các lần kiểm tra khác cung cấp xác minh trạng thái (và mô phỏng tùy chọn) trong khi mô phỏng cung cấp xác minh hành vi (và mô phỏng tùy chọn).

Với một sơ khai, bạn có thể gọi một số phương thức trên sơ khai theo bất kỳ thứ tự nào (hoặc thậm chí là có trách nhiệm) và xác định thành công nếu sơ khai đó đã chiếm được một giá trị hoặc trạng thái bạn dự định. Ngược lại, một đối tượng giả sẽ mong đợi các hàm rất cụ thể được gọi, theo một thứ tự cụ thể và thậm chí là một số lần cụ thể. Thử nghiệm với một đối tượng giả sẽ được coi là "thất bại" đơn giản vì các phương thức được gọi theo một trình tự hoặc số khác nhau - ngay cả khi đối tượng giả có trạng thái chính xác khi thử nghiệm kết thúc!

Theo cách này, các đối tượng giả thường được coi là kết hợp chặt chẽ hơn với mã SUT so với các đối tượng còn sơ khai. Đó có thể là một điều tốt hoặc xấu, tùy thuộc vào những gì bạn đang cố gắng xác minh.


3

Một phần của điểm sử dụng các đối tượng giả là chúng không phải được thực hiện theo thông số kỹ thuật. Họ chỉ có thể đưa ra phản ứng giả. Ví dụ: nếu bạn phải triển khai các thành phần A và B và cả hai "cuộc gọi" (tương tác) với nhau, thì bạn không thể kiểm tra A cho đến khi B được triển khai và ngược lại. Trong phát triển dựa trên thử nghiệm, đây là một vấn đề. Vì vậy, bạn tạo các đối tượng giả ("giả") cho A và B, rất đơn giản, nhưng chúng đưa ra một số phản hồi khi chúng tương tác. Bằng cách đó, bạn có thể thực hiện và kiểm tra A bằng cách sử dụng một đối tượng giả cho B.


1

Đối với php và phpunit được giải thích tốt trong tài liệu phpunit. xem ở đây tài liệu phpunit

Trong đối tượng nhạo báng từ đơn giản chỉ là đối tượng giả của đối tượng ban đầu của bạn và được trả về giá trị trả về của nó, giá trị trả về này có thể được sử dụng trong lớp kiểm tra


0

Đó là một trong những quan điểm chính của các bài kiểm tra đơn vị. có, bạn đang cố kiểm tra đơn vị mã của mình và kết quả kiểm tra của bạn không liên quan đến hành vi của các đối tượng hoặc đối tượng khác. vì vậy bạn nên chế nhạo chúng bằng cách sử dụng các đối tượng Mock với một số phản hồi tương ứng đơn giản hóa.

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.