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 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 .
Câu trả lời:
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.
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 MailServer
giao 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 MailServer
giao 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.
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 MySorter
lớ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.
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 PdfFormatter
tượ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:
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.TestPdfFormatter
mà 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 .
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ả.
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