Khi nào tôi nên chế giễu?


137

Tôi có hiểu biết cơ bản về các đối tượng giả và giả, nhưng tôi không chắc mình có cảm giác về việc sử dụng chế độ giả khi nào / ở đâu - đặc biệt là khi áp dụng cho kịch bản này ở đây .


Tôi khuyên bạn chỉ nên chế giễu các phụ thuộc ngoài quy trình và chỉ những phụ thuộc trong số chúng, các tương tác có thể quan sát được bên ngoài (máy chủ SMTP, bus tin nhắn, v.v.). Đừng chế nhạo cơ sở dữ liệu, đó là một chi tiết triển khai. Thông tin thêm về nó ở đây: enterpriseccraft
Skill.com/posts/when-to-mock

Câu trả lời:


121

Một bài kiểm tra đơn vị nên kiểm tra một bản mã thông qua một phương thức duy nhất. Khi việc thực hiện một phương thức vượt ra ngoài phương thức đó, vào một đối tượng khác và quay lại, bạn có một sự phụ thuộc.

Khi bạn kiểm tra đường dẫn mã đó với sự phụ thuộc thực tế, bạn không phải là kiểm tra đơn vị; bạn đang thử nghiệm tích hợp. Mặc dù điều đó tốt và cần thiết, nhưng nó không phải là thử nghiệm đơn vị.

Nếu sự phụ thuộc của bạn là lỗi, thử nghiệm của bạn có thể bị ảnh hưởng theo cách như vậy để trả về kết quả dương tính giả. Chẳng hạn, bạn có thể chuyển phần phụ thuộc thành null không mong muốn và phần phụ thuộc có thể không chuyển sang null vì nó được ghi lại. Thử nghiệm của bạn không gặp phải ngoại lệ đối số null như mong muốn và thử nghiệm đã vượt qua.

Ngoài ra, bạn có thể thấy khó, nếu không thể, đáng tin cậy để khiến đối tượng phụ thuộc trả lại chính xác những gì bạn muốn trong một bài kiểm tra. Điều đó cũng bao gồm ném ngoại lệ dự kiến ​​trong các bài kiểm tra.

Một mock thay thế sự phụ thuộc đó. Bạn đặt kỳ vọng vào các cuộc gọi đến đối tượng phụ thuộc, đặt các giá trị trả về chính xác mà nó sẽ cung cấp cho bạn để thực hiện kiểm tra bạn muốn và / hoặc ngoại lệ nào để ném để bạn có thể kiểm tra mã xử lý ngoại lệ của mình. Bằng cách này, bạn có thể kiểm tra các đơn vị trong câu hỏi dễ dàng.

TL; DR: Giả lập mọi phụ thuộc mà bài kiểm tra đơn vị của bạn chạm vào.


164
Câu trả lời này quá triệt để. Các bài kiểm tra đơn vị có thể và nên thực hiện nhiều hơn một phương pháp, miễn là tất cả thuộc về cùng một đơn vị gắn kết. Làm khác đi sẽ đòi hỏi quá nhiều cách chế giễu / giả mạo, dẫn đến các bài kiểm tra phức tạp và dễ vỡ. Chỉ nên thay thế các phụ thuộc không thực sự thuộc về đơn vị được thử nghiệm thông qua chế độ chế nhạo.
Rogério

10
Câu trả lời này cũng quá lạc quan. Sẽ tốt hơn nếu nó kết hợp những thiếu sót của @ Jan đối với các đối tượng giả.
Jeff Axelrod

1
Đây không phải là một cuộc tranh luận về việc tiêm phụ thuộc vào các xét nghiệm chứ không phải là giả định cụ thể sao? Bạn có thể thay thế "mock" bằng "stub" trong câu trả lời của bạn. Tôi đồng ý rằng bạn nên chế giễu hoặc bỏ qua các phụ thuộc đáng kể. Tôi đã thấy rất nhiều mã giả nặng về cơ bản kết thúc việc thực hiện lại các phần của các đối tượng bị chế giễu; giả chắc chắn không phải là một viên đạn bạc.
Draemon

2
Mock mọi phụ thuộc kiểm tra đơn vị của bạn chạm. Cái này giải thích tất cả.
Teoman shipahi

2
TL; DR: Giả lập mọi phụ thuộc mà bài kiểm tra đơn vị của bạn chạm vào. - đây không thực sự là một cách tiếp cận tuyệt vời, mockito tự nói - đừng chế giễu mọi thứ. (được đánh giá thấp)
p_champ

167

Các đối tượng giả rất hữu ích khi bạn muốn kiểm tra các tương tác giữa một lớp được kiểm tra và một giao diện cụ thể.

Ví dụ, chúng tôi muốn kiểm tra phương thức đó sendInvitations(MailServer mailServer)gọi MailServer.createMessage()chính xác một lần và cũng gọi MailServer.sendMessage(m)chính xác một lần và không có phương thức nào khác được gọi trên MailServergiao diện. Đây là khi chúng ta có thể sử dụng các đối tượng giả.

Với các đối tượng giả, thay vì vượt qua một thực tế MailServerImpl, hoặc một bài kiểm tra TestMailServer, chúng ta có thể vượt qua một triển khai giả của MailServergiao diện. Trước khi chúng ta vượt qua một bản giả MailServer, chúng ta "huấn luyện" nó, để nó biết phương thức nào gọi là mong đợi và giá trị trả về nào sẽ trả về. Cuối cùng, đối tượng giả khẳng định rằng tất cả các phương thức dự kiến ​​được gọi là như mong đợi.

Điều này nghe có vẻ tốt trong lý thuyết, nhưng cũng có một số nhược điểm.

Thiếu sót giả

Nếu bạn có một khung giả, tại chỗ, bạn nên sử dụng đối tượng giả mỗi khi bạn cần chuyển giao diện cho lớp theo bài kiểm tra. Bằng cách này, bạn kết thúc thử nghiệm các tương tác ngay cả khi không cần thiết . Thật không may, việc kiểm tra các tương tác không mong muốn (vô tình) là không tốt, bởi vì sau đó bạn đang kiểm tra rằng một yêu cầu cụ thể được thực hiện theo một cách cụ thể, thay vì việc thực hiện đó tạo ra kết quả cần thiết.

Đây là một ví dụ trong mã giả. Giả sử chúng ta đã tạo một MySorterlớp và chúng tôi muốn kiểm tra nó:

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(Trong ví dụ này, chúng tôi giả định rằng đó không phải là một thuật toán sắp xếp cụ thể, chẳng hạn như sắp xếp nhanh, mà chúng tôi muốn kiểm tra; trong trường hợp đó, thử nghiệm sau sẽ thực sự hợp lệ.)

Trong một ví dụ cực đoan như vậy, rõ ràng tại sao ví dụ sau lại sai. Khi chúng tôi thay đổi việc thực hiện MySorter, thử nghiệm đầu tiên thực hiện rất tốt việc đảm bảo chúng tôi vẫn sắp xếp chính xác, đó là toàn bộ điểm của các thử nghiệm - chúng cho phép chúng tôi thay đổi mã một cách an toàn. Mặt khác, bài kiểm tra sau luôn bị phá vỡ và nó có hại tích cực; nó cản trở tái cấu trúc.

Giả như cuống

Các khung giả định thường cho phép sử dụng ít nghiêm ngặt hơn, trong đó chúng ta không phải chỉ định chính xác số lần gọi các phương thức và các tham số nào được mong đợi; chúng cho phép tạo các đối tượng giả được sử dụng làm sơ khai .

Giả sử chúng ta có một phương pháp sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)mà chúng ta muốn thử nghiệm. Đối PdfFormattertượng có thể được sử dụng để tạo lời mời. Đây là bài kiểm tra:

testInvitations() {
   // train as stub
   pdfFormatter = create mock of PdfFormatter
   let pdfFormatter.getCanvasWidth() returns 100
   let pdfFormatter.getCanvasHeight() returns 300
   let pdfFormatter.addText(x, y, text) returns true 
   let pdfFormatter.drawLine(line) does nothing

   // train as mock
   mailServer = create mock of MailServer
   expect mailServer.sendMail() called exactly once

   // do the test
   sendInvitations(pdfFormatter, mailServer)

   assert that all pdfFormatter expectations are met
   assert that all mailServer expectations are met
}

Trong ví dụ này, chúng tôi không thực sự quan tâm đến PdfFormatterđối tượng nên chúng tôi chỉ huấn luyện nó để lặng lẽ chấp nhận bất kỳ cuộc gọi nào và trả về một số giá trị trả về đóng hộp hợp lý cho tất cả các phương thức sendInvitation()xảy ra để gọi vào thời điểm này. Làm thế nào chúng tôi đưa ra chính xác danh sách các phương pháp này để đào tạo? Chúng tôi chỉ đơn giản là chạy thử nghiệm và tiếp tục thêm các phương thức cho đến khi thử nghiệm vượt qua. Lưu ý rằng chúng tôi đã đào tạo sơ khai để đáp ứng với một phương thức mà không có manh mối tại sao cần phải gọi nó, chúng tôi chỉ cần thêm mọi thứ mà bài kiểm tra phàn nàn. Chúng tôi rất vui, bài kiểm tra đã qua.

Nhưng điều gì xảy ra sau đó, khi chúng ta thay đổi sendInvitations(), hoặc một số lớp khác sendInvitations()sử dụng, để tạo các tệp pdf ưa thích hơn? Thử nghiệm của chúng tôi đột nhiên thất bại vì bây giờ có nhiều phương thức PdfFormatterđược gọi hơn và chúng tôi đã không đào tạo được sơ khai của mình để mong đợi chúng. Và thông thường, không chỉ có một thử nghiệm thất bại trong các tình huống như thế này, đó là bất kỳ thử nghiệm nào xảy ra để sử dụng, trực tiếp hoặc gián tiếp, sendInvitations()phương pháp. Chúng tôi phải sửa tất cả các bài kiểm tra bằng cách thêm nhiều khóa đào tạo. Cũng lưu ý rằng chúng tôi không thể xóa các phương thức không còn cần thiết nữa, vì chúng tôi không biết phương pháp nào không cần thiết. Một lần nữa, nó cản trở tái cấu trúc.

Ngoài ra, khả năng đọc thử nghiệm bị ảnh hưởng khủng khiếp, có rất nhiều mã mà chúng tôi đã không viết vì chúng tôi muốn, nhưng vì chúng tôi phải làm vậy; Không phải chúng tôi, những người muốn mã đó. Các thử nghiệm sử dụng các đối tượng giả trông rất phức tạp và thường khó đọc. Các bài kiểm tra sẽ giúp người đọc hiểu, làm thế nào lớp dưới bài kiểm tra nên được sử dụng, do đó chúng nên đơn giản và dễ hiểu. Nếu chúng không thể đọc được, không ai sẽ duy trì chúng; thực tế, việc xóa chúng dễ dàng hơn là duy trì chúng.

Làm thế nào để khắc phục điều đó? Dễ dàng:

  • Hãy thử sử dụng các lớp học thực sự thay vì chế nhạo bất cứ khi nào có thể. Sử dụng thực tế PdfFormatterImpl. Nếu không thể, hãy thay đổi các lớp thực để biến nó thành có thể. Không thể sử dụng một lớp trong các bài kiểm tra thường chỉ ra một số vấn đề với lớp. Khắc phục sự cố là một tình huống có lợi - bạn đã sửa lớp và bạn có một bài kiểm tra đơn giản hơn. Mặt khác, không sửa nó và sử dụng giả là một tình huống không thể thắng - bạn đã không sửa lớp thực và bạn có các bài kiểm tra phức tạp hơn, ít đọc hơn, cản trở việc tái cấu trúc hơn nữa.
  • Hãy thử tạo một triển khai thử nghiệm đơn giản của giao diện thay vì chế nhạo nó trong mỗi thử nghiệm và sử dụng lớp thử nghiệm này trong tất cả các thử nghiệm của bạn. Tạo TestPdfFormattermà không làm gì cả. Bằng cách đó, bạn có thể thay đổi nó một lần cho tất cả các bài kiểm tra và bài kiểm tra của bạn không bị lộn xộn với các thiết lập dài nơi bạn huấn luyện sơ khai của mình.

Nói chung, các đối tượng giả có công dụng của chúng, nhưng khi không được sử dụng cẩn thận, chúng thường khuyến khích các thực hành xấu, kiểm tra chi tiết thực hiện, cản trở tái cấu trúc và tạo ra các bài kiểm tra khó đọc và khó đọc .

Để biết thêm chi tiết về những thiếu sót của giả, xem thêm Đối tượng giả: Thiếu sót và Các trường hợp sử dụng .


1
Một câu trả lời cũng được nghĩ ra, và tôi chủ yếu đồng ý. Tôi sẽ nói rằng vì các thử nghiệm đơn vị là thử nghiệm hộp trắng, nên phải thay đổi các thử nghiệm khi bạn thay đổi triển khai để gửi các tệp PDF fancier có thể không phải là một gánh nặng vô lý. Đôi khi, giả có thể là cách hữu ích để nhanh chóng thực hiện sơ khai thay vì có nhiều tấm nồi hơi. Trong thực tế, có vẻ như việc sử dụng chúng không bị hạn chế trong những trường hợp đơn giản này, tuy nhiên.
Draemon

1
Không phải toàn bộ vấn đề giả là các bài kiểm tra của bạn đều nhất quán, rằng bạn không phải lo lắng về việc chế nhạo các đối tượng mà việc triển khai liên tục thay đổi có thể bởi các lập trình viên khác mỗi khi bạn chạy thử nghiệm và bạn có được kết quả kiểm tra nhất quán không?
positiveGuy

1
Điểm rất tốt và có liên quan (đặc biệt là về các bài kiểm tra tính mong manh). Tôi đã từng sử dụng giả rất nhiều khi tôi còn trẻ, nhưng bây giờ tôi coi thử nghiệm đơn vị rằng độ nặng phụ thuộc vào giả là có khả năng dùng một lần và tập trung nhiều hơn vào thử nghiệm tích hợp (với các thành phần thực tế)
Kemoda

6
"Không thể sử dụng một lớp trong các bài kiểm tra thường chỉ ra một số vấn đề với lớp." Nếu lớp là một dịch vụ (ví dụ: quyền truy cập vào cơ sở dữ liệu hoặc proxy cho dịch vụ web), thì nó nên được coi là một phụ thuộc bên ngoài và bị chế giễu / sơ khai
Michael Freidgeim

1
Nhưng điều gì xảy ra sau đó, khi chúng ta thay đổi sendInvitations ()? Nếu mã được kiểm tra bị sửa đổi, nó không đảm bảo hợp đồng trước đó nữa, do đó nó phải thất bại. Và thông thường, không chỉ có một bài kiểm tra thất bại trong các tình huống như thế này . Nếu đây là trường hợp, mã không được thực hiện sạch. Việc xác minh các cuộc gọi phương thức của phụ thuộc chỉ nên được kiểm tra một lần (trong thử nghiệm đơn vị thích hợp). Tất cả các lớp khác sẽ chỉ sử dụng ví dụ giả. Vì vậy, tôi không thấy bất kỳ lợi ích nào khi tích hợp - với các bài kiểm tra đơn vị.
Christopher Will

55

Quy tắc của ngón tay cái:

Nếu chức năng bạn đang kiểm tra cần một đối tượng phức tạp làm tham số và sẽ rất khó để khởi tạo đối tượng này (ví dụ: nếu nó cố gắng thiết lập kết nối TCP), hãy sử dụng giả.


4

Bạn nên chế nhạo một đối tượng khi bạn có một phụ thuộc trong một đơn vị mã mà bạn đang cố kiểm tra cần phải "chỉ như vậy".

Ví dụ, khi bạn đang cố kiểm tra một số logic trong đơn vị mã của mình nhưng bạn cần lấy thứ gì đó từ một đối tượng khác và những gì được trả về từ sự phụ thuộc này có thể ảnh hưởng đến những gì bạn đang cố kiểm tra - giả định đối tượng đó.

Một podcast tuyệt vời về chủ đề có thể được tìm thấy ở đây


Liên kết bây giờ định tuyến đến tập hiện tại, không phải tập dự định. Là podcast dự định này hanselminutes.com/32/mock-objects ?
C Perkins
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.