Đơn vị kiểm tra một lớp sử dụng DI mà không kiểm tra nội bộ


12

Tôi có một lớp được tái cấu trúc trong 1 lớp chính và 2 lớp nhỏ hơn. Các lớp chính sử dụng cơ sở dữ liệu (giống như nhiều lớp của tôi làm) và gửi email. Vì vậy, lớp chính có một IPersonRepositoryvà một IEmailRepositorytiêm mà đến lượt nó gửi đến 2 lớp nhỏ hơn.

Bây giờ tôi muốn kiểm tra đơn vị lớp chính, và đã học cách không bỏ qua các hoạt động nội bộ của lớp, bởi vì chúng tôi sẽ có thể thay đổi hoạt động nội bộ mà không phá vỡ các bài kiểm tra đơn vị.

Nhưng khi lớp sử dụng IPersonRepositoryvà an IEmailRepository, tôi PHẢI chỉ định kết quả (giả / giả) cho một số phương thức cho IPersonRepository. Lớp chính tính toán một số dữ liệu dựa trên dữ liệu hiện có và trả về dữ liệu đó. Nếu tôi muốn kiểm tra điều đó, tôi không thấy làm thế nào tôi có thể viết một bài kiểm tra mà không chỉ định rằng IPersonRepository.GetSavingsByCustomerIdtrả về x. Nhưng sau đó, bài kiểm tra đơn vị của tôi 'biết' về hoạt động nội bộ, bởi vì nó 'biết' phương pháp nào để chế giễu và phương pháp nào không.

Làm thế nào tôi có thể kiểm tra một lớp đã tiêm phụ thuộc mà không cần kiểm tra biết về các phần bên trong?

lý lịch:

Theo kinh nghiệm của tôi, rất nhiều thử nghiệm như thế này tạo ra các giả cho các kho lưu trữ và sau đó cung cấp dữ liệu phù hợp cho các giả hoặc kiểm tra nếu một phương thức cụ thể được gọi trong khi thực thi. Dù bằng cách nào, bài kiểm tra biết về nội bộ.

Bây giờ tôi đã thấy một bài trình bày về lý thuyết (mà tôi đã nghe trước đây) rằng bài kiểm tra không nên biết về việc thực hiện. Đầu tiên bởi vì bạn không kiểm tra cách thức hoạt động của nó, mà còn bởi vì khi bạn thay đổi việc triển khai, tất cả các kiểm tra đơn vị đều thất bại vì họ 'biết' về việc triển khai. Mặc dù tôi thích khái niệm về các bài kiểm tra mà không biết về việc thực hiện, tôi không biết làm thế nào để hoàn thành nó.


1
Ngay khi lớp chính của bạn mong đợi một IPersonRepositoryđối tượng, giao diện đó và tất cả các phương thức mà nó mô tả không còn là "nội bộ" nữa, vì vậy nó không thực sự là một vấn đề của bài kiểm tra. Câu hỏi thực sự của bạn phải là "làm thế nào tôi có thể cấu trúc lại các lớp thành các đơn vị nhỏ hơn mà không để lộ quá nhiều ở nơi công cộng". Câu trả lời là "giữ cho các giao diện đó nạc" (ví dụ, bằng cách tuân thủ nguyên tắc phân chia giao diện). Đó là IMHO điểm 2 trong câu trả lời của @ DavidArno (Tôi đoán rằng tôi không cần phải lặp lại điều đó trong câu trả lời khác).
Doc Brown

Câu trả lời:


7

Lớp chính tính toán một số dữ liệu dựa trên dữ liệu hiện có và trả về dữ liệu đó. Nếu tôi muốn kiểm tra điều đó, tôi không thấy làm thế nào tôi có thể viết một bài kiểm tra mà không chỉ định rằng IPersonRepository.GetSavingsByCustomerIdtrả về x. Nhưng sau đó, bài kiểm tra đơn vị của tôi 'biết' về hoạt động nội bộ, bởi vì nó 'biết' phương pháp nào để chế giễu và phương pháp nào không.

Bạn đúng rằng điều này vi phạm nguyên tắc "không kiểm tra nội bộ" và là một điều phổ biến mà mọi người bỏ qua.

Làm thế nào tôi có thể kiểm tra một lớp đã tiêm phụ thuộc mà không cần kiểm tra biết về các phần bên trong?

Có hai giải pháp mà bạn có thể áp dụng để khắc phục vi phạm này:

1) Cung cấp một mock hoàn chỉnh của IPersonRepository. Cách tiếp cận được mô tả hiện tại của bạn là kết hợp giả với các hoạt động bên trong của phương thức đang được thử nghiệm bằng cách chỉ chế giễu các phương thức mà nó sẽ gọi. Nếu bạn cung cấp giả cho tất cả các phương pháp IPersonRepository, thì bạn loại bỏ khớp nối đó. Các hoạt động bên trong có thể thay đổi mà không ảnh hưởng đến giả, do đó làm cho thử nghiệm ít giòn hơn.

Cách tiếp cận này có ưu điểm là giữ cho cơ chế DI đơn giản, nhưng nó có thể tạo ra nhiều công việc nếu giao diện của bạn xác định nhiều phương thức.

2) Không tiêm IPersonRepository, tiêm GetSavingsByCustomerIdphương pháp hoặc tiêm giá trị tiết kiệm. Vấn đề với việc thực thi toàn bộ giao diện là bạn đang tiêm ("nói, không hỏi") một hệ thống "hỏi, không nói", trộn lẫn hai cách tiếp cận. Nếu bạn thực hiện một cách tiếp cận "thuần DI", phương thức sẽ được cung cấp (nói) phương thức chính xác để gọi nếu nó muốn một giá trị tiết kiệm, thay vì được cung cấp một đối tượng (sau đó nó phải yêu cầu phương thức gọi một cách hiệu quả).

Ưu điểm của phương pháp này là nó tránh được sự cần thiết của giả (ngoài các phương pháp thử nghiệm mà bạn tiêm vào phương pháp đang thử nghiệm). Nhược điểm là nó có thể làm cho chữ ký của phương thức thay đổi để đáp ứng với các yêu cầu của phương pháp thay đổi.

Cả hai phương pháp đều có ưu và nhược điểm, vì vậy hãy chọn phương pháp phù hợp nhất với nhu cầu của bạn.


1
Tôi thực sự thích ý tưởng số 2. Tôi không biết tại sao tôi chưa từng nghĩ đến điều đó trước đây. Tôi đã tạo ra sự phụ thuộc vào Kho lưu trữ trong một vài năm nay mà không tự hỏi mình tại sao tôi lại tạo ra sự phụ thuộc vào toàn bộ Kho lưu trữ (trong nhiều trường hợp sẽ chứa khá nhiều chữ ký phương thức) trong khi nó chỉ phụ thuộc vào một hoặc 2 phương pháp. Vì vậy, thay vì tiêm một IRep repository, tôi có thể tiêm hoặc chuyển tốt hơn chữ ký phương thức (chỉ) cần thiết.
Michel

1

Cách tiếp cận của tôi là tạo các phiên bản 'giả' của các kho lưu trữ đọc từ các tệp đơn giản chứa dữ liệu cần thiết.

Điều này có nghĩa là thử nghiệm riêng lẻ không 'biết' về việc thiết lập giả, mặc dù rõ ràng dự án thử nghiệm tổng thể sẽ tham chiếu giả và có các tệp cài đặt, v.v.

Điều này tránh thiết lập đối tượng giả phức tạp được yêu cầu bởi các khung mô phỏng và cho phép bạn sử dụng các đối tượng 'giả' trong các phiên bản thực của ứng dụng của bạn để kiểm tra giao diện người dùng và tương tự.

Vì 'mock' được triển khai đầy đủ, thay vì thiết lập cụ thể cho kịch bản thử nghiệm của bạn, ví dụ như thay đổi triển khai, ví dụ, giả sử GetSavingsForCustomer cũng sẽ xóa khách hàng. Sẽ không phá vỡ các bài kiểm tra (trừ khi tất nhiên nó thực sự phá vỡ các bài kiểm tra), bạn chỉ cần cập nhật triển khai giả đơn và tất cả các bài kiểm tra của bạn sẽ chạy với nó mà không thay đổi thiết lập của chúng


2
Điều này vẫn dẫn đến tình huống tôi tạo ra một bản giả với dữ liệu cho chính xác các phương thức mà lớp được thử nghiệm đang làm việc với. Vì vậy, tôi vẫn có một vấn đề là khi thay đổi thực hiện, thử nghiệm sẽ bị hỏng.
Michel

1
được chỉnh sửa để giải thích lý do tại sao cách tiếp cận của tôi tốt hơn, mặc dù vẫn không hoàn hảo cho kịch bản tôi nghĩ bạn muốn nói
Ewan

1

Các bài kiểm tra đơn vị thường là các bài kiểm tra whitebox (bạn có quyền truy cập vào mã thực). Do đó, bạn có thể biết nội bộ ở một mức độ nhất định, tuy nhiên đối với người mới bắt đầu thì không nên, vì bạn không nên kiểm tra hành vi nội bộ (chẳng hạn như "gọi phương thức a trước, sau đó b và sau đó lại").

Tiêm một giả cung cấp dữ liệu là được, vì lớp (đơn vị) của bạn phụ thuộc vào dữ liệu ngoài đó (hoặc nhà cung cấp dữ liệu). Tuy nhiên, bạn nên xác minh kết quả (không phải là cách để đến với họ)! ví dụ: bạn cung cấp một cá thể Person và xác minh rằng một email đã được gửi đến đúng địa chỉ email, ví dụ: bằng cách cung cấp một bản giả cho email, giả đó không làm gì ngoài việc lưu địa chỉ email của người nhận để truy cập sau bằng thử nghiệm của bạn -code. (Tuy nhiên, tôi nghĩ Martin Fowler gọi chúng là Stub chứ không phải Mocks)

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.