TDD: Mocking ra các đối tượng kết hợp chặt chẽ


10

Đôi khi các đối tượng chỉ cần được gắn chặt. Ví dụ, một CsvFilelớp có thể sẽ cần phải làm việc chặt chẽ với CsvRecordlớp (hoặc ICsvRecordgiao diện).

Tuy nhiên, từ những gì tôi học được trong quá khứ, một trong những nguyên lý chính của phát triển dựa trên thử nghiệm là "Không bao giờ kiểm tra nhiều hơn một lớp cùng một lúc." Có nghĩa là bạn nên sử dụng giả ICsvRecordhoặc sơ khai thay vì các trường hợp thực tế CsvRecord.

Tuy nhiên, sau khi thử phương pháp này, tôi nhận thấy rằng việc loại bỏ CsvRecordlớp học có thể có một ít lông. Dẫn tôi đến một trong hai kết luận:

  1. Thật khó để viết bài kiểm tra đơn vị! Đó là một mùi mã! Cấu trúc lại!
  2. Nhạo báng mọi sự phụ thuộc duy nhất là không hợp lý.

Khi tôi thay thế các giả của mình bằng các CsvRecordtrường hợp thực tế , mọi thứ diễn ra suôn sẻ hơn nhiều. Khi tìm kiếm những suy nghĩ của người khác, tôi tình cờ thấy bài đăng trên blog này , có vẻ như hỗ trợ # 2 ở trên. Đối với các đối tượng được liên kết chặt chẽ một cách tự nhiên, chúng ta không nên lo lắng quá nhiều về việc chế nhạo.

Tôi có cách không theo dõi? Có bất kỳ nhược điểm nào đối với giả định # 2 ở trên không? Tôi thực sự nên suy nghĩ về việc tái cấu trúc thiết kế của tôi?


1
Tôi nghĩ rằng đó là một quan niệm sai lầm phổ biến rằng "đơn vị" trong "bài kiểm tra đơn vị" nhất thiết phải là một lớp. Tôi nghĩ rằng ví dụ của bạn cho thấy một trường hợp có thể tốt hơn khi hai lớp đó tạo thành một đơn vị. Nhưng đừng hiểu lầm tôi, tôi hoàn toàn đồng ý với câu trả lời của Robert Harvey.
Doc Brown

Câu trả lời:


11

Nếu bạn thực sự cần sự phối hợp giữa hai lớp đó, hãy viết một CsvCoordinatorlớp gói gọn hai lớp của bạn và kiểm tra nó.

Tuy nhiên, tôi tranh luận về khái niệm CsvRecordkhông thể kiểm chứng độc lập. CsvRecordvề cơ bản là một lớp DTO , phải không? Nó chỉ là một tập hợp các trường, có thể có một vài phương thức trợ giúp. Và CsvRecordcó thể được sử dụng trong các bối cảnh khác bên cạnh CsvFile; bạn có thể có một bộ sưu tập hoặc mảng CsvRecords chẳng hạn.

Kiểm tra CsvRecordtrước. Hãy chắc chắn rằng nó vượt qua tất cả các bài kiểm tra của nó. Sau đó, tiếp tục và sử dụng CsvRecordvới CsvFilelớp của bạn trong quá trình kiểm tra. Sử dụng nó như một sơ khai / giả thử nghiệm trước; điền nó với dữ liệu kiểm tra có liên quan, chuyển nó đến CsvFilevà viết các trường hợp kiểm tra của bạn chống lại điều đó.


1
Có, CsvRecord hoàn toàn có thể kiểm tra độc lập. Vấn đề là nếu một cái gì đó bị hỏng trong CsvRecord, nó sẽ khiến các thử nghiệm CsvData không thành công. Nhưng tôi không nghĩ đó là một vấn đề lớn.
Phil

1
Tôi nghĩ rằng bạn muốn điều đó xảy ra. :)
Robert Harvey

1
@RobertHarvey: về lý thuyết, nó có thể trở thành vấn đề nếu CsvRecord và CsvFile đang trở thành các lớp khá phức tạp và nếu một bài kiểm tra bị phá vỡ đối với CsvFile, bây giờ bạn không biết ngay lập tức nếu đó là vấn đề trong CsvFile hoặc CsvRecord. Nhưng tôi đoán đó là một trường hợp giả định - nếu tôi có nhiệm vụ lập trình các lớp như vậy cho một chương trình trong thế giới thực, tôi sẽ thực hiện chính xác như cách bạn mô tả.
Doc Brown

2
@Phil: Nếu CsvRecordnghỉ thì rõ ràng là CsvDatathất bại; nhưng điều này không sao, bởi vì bạn kiểm tra CsvRecordtrước và nếu thất bại, các CsvFilebài kiểm tra của bạn là vô nghĩa. Bạn vẫn có thể phân biệt giữa lỗi trong CsvRecordvà trong CsvFile.
tdammers

5

Lý do để kiểm tra một lớp tại một thời điểm là bạn không muốn các bài kiểm tra cho một lớp có sự phụ thuộc vào hành vi của lớp thứ hai. Điều đó có nghĩa là nếu bài kiểm tra của bạn cho Lớp A thực hiện bất kỳ chức năng nào của Lớp B, thì bạn nên chế nhạo Lớp B để loại bỏ sự phụ thuộc vào chức năng cụ thể trong Lớp B.

CsvRecordĐối với tôi, một lớp dường như chủ yếu là để lưu trữ dữ liệu - đó không phải là một lớp có quá nhiều chức năng của riêng nó. Đó là, nó có thể có các hàm tạo, getters, setters, nhưng không có phương thức nào với logic thực sự đáng kể. Tất nhiên, tôi đoán ở đây - có thể bạn đã viết một lớp gọi là CsvRecordthực hiện nhiều phép tính phức tạp.

Nhưng nếu CsvRecordkhông có logic thực sự của riêng nó, sẽ không có gì đạt được bằng cách chế nhạo nó. Đây thực sự chỉ là câu châm ngôn cũ - "không chế nhạo các đối tượng giá trị" .

Vì vậy, khi xem xét có nên giả định một lớp cụ thể (đối với một bài kiểm tra của một lớp khác), bạn nên tính đến mức độ logic của chính lớp đó và bao nhiêu logic đó sẽ được thực hiện trong quá trình kiểm tra của bạn.


+1. Bất kỳ thử nghiệm nào có kết quả phụ thuộc vào tính chính xác của nhiều hành vi của một đối tượng là thử nghiệm tích hợp, không phải thử nghiệm đơn vị. Bạn phải giả định một trong những đối tượng này để có được bài kiểm tra đơn vị thực sự. Điều này không áp dụng cho các đối tượng không có hành vi thực sự trong đó mặc dù - chỉ với getters và setters chẳng hạn.
guillaume31

1

Số 2 là tốt. Mọi thứ có thể, và nên được kết hợp chặt chẽ nếu các khái niệm của chúng được liên kết chặt chẽ. Điều này nên hiếm, và thường tránh, nhưng trong ví dụ bạn cung cấp nó có ý nghĩa.


0

Các lớp "ghép" phụ thuộc lẫn nhau . Đây không phải là trường hợp trong những gì bạn đang mô tả - một CsvRecord không thực sự quan tâm đến CsvFile có chứa nó, vì vậy sự phụ thuộc chỉ đi theo một chiều. Điều đó tốt, và không khớp nối chặt chẽ.

Rốt cuộc, nếu một lớp chứa tên Chuỗi biến, bạn sẽ không cho rằng nó được liên kết chặt chẽ với Chuỗi, phải không?

Vì vậy, đơn vị kiểm tra CsvRecord cho hành vi mong muốn của nó.

Sau đó, sử dụng khung mô phỏng (Mockito là tuyệt vời) để kiểm tra xem đơn vị của bạn có tương tác với các đối tượng mà nó phụ thuộc chính xác hay không. Hành vi bạn muốn kiểm tra, thực sự - là CsvFile xử lý CsvRcords theo cách mong đợi. Hoạt động bên trong của CvsRecord không quan trọng - đó là cách CvsFile giao tiếp với nó.

Cuối cùng, TDD không chỉ là về các bài kiểm tra đơn vị. Bạn chắc chắn có thể (và nên) bắt đầu với các thử nghiệm chức năng xem xét hành vi chức năng về cách thức các thành phần lớn hơn của bạn hoạt động - đó là câu chuyện hoặc kịch bản người dùng của bạn. Các bài kiểm tra đơn vị của bạn đặt kỳ vọng và xác minh các phần, các bài kiểm tra chức năng làm tương tự cho toàn bộ.


1
-1, khớp nối chặt chẽ không nhất thiết có nghĩa là phụ thuộc theo chu kỳ, đó là một quan niệm sai lầm. Trong ví dụ, CsvFile được liên kết chặt chẽ với CsvRecord(nhưng không phải là cách khác). OP hỏi liệu có nên thử nghiệm hay không CsvFilebằng cách tách nó ra CsvRecordthông qua một ICsvRecordchứ không phải ngược lại.
Doc Brown

2
@DocBrown: Việc khớp nối có chặt chẽ hay không là vấn đề bao nhiêu CsvFiletùy thuộc vào hoạt động bên trong của CsvRecord, nghĩa là, số lượng giả định mà tệp có về hồ sơ. Các giao diện giúp ghi lại và thực thi các giả định đó (hay đúng hơn là không có các giả định khác), nhưng số lượng khớp nối vẫn giữ nguyên, ngoại trừ với một giao diện, bạn có thể nối một lớp bản ghi khác vào CsvFile. Giới thiệu giao diện chỉ để bạn có thể nói rằng bạn đã giảm khớp nối là ngớ ngẩn.
tdammers

0

Thực sự có hai câu hỏi ở đây. Đầu tiên là nếu tình huống tồn tại trong đó việc chế nhạo một đối tượng là không thể. Điều đó chắc chắn là đúng, như thể hiện bởi các câu trả lời xuất sắc khác. Câu hỏi thứ hai là liệu trường hợp cụ thể của bạn là một trong những tình huống đó. Về câu hỏi đó tôi không bị thuyết phục.

Có lẽ lý do phổ biến nhất để không chế nhạo một lớp là nếu đó là một lớp giá trị. Tuy nhiên, bạn phải xem xét lý do đằng sau quy tắc. Không phải vì lớp bị chế giễu sẽ xấu đi bằng cách nào đó, bởi vì về cơ bản nó sẽ giống với bản gốc. Nếu đó là trường hợp, thử nghiệm đơn vị của bạn sẽ không dễ dàng hơn bằng cách sử dụng lớp ban đầu.

Rất có thể mã của bạn là một trong những trường hợp ngoại lệ hiếm hoi khi tái cấu trúc sẽ không giúp ích gì, nhưng bạn chỉ nên khai báo như vậy sau khi các nỗ lực tái cấu trúc siêng năng không hoạt động. Ngay cả các nhà phát triển dày dạn cũng có thể gặp khó khăn khi nhìn thấy các lựa chọn thay thế cho thiết kế của riêng họ. Nếu bạn không thể nghĩ ra bất kỳ cách nào có thể để cải thiện nó, hãy nhờ ai đó có kinh nghiệm cung cấp cho nó một cái nhìn thứ hai.

Hầu hết mọi người dường như đang cho rằng bạn CsvRecordlà một lớp giá trị. Hãy thử làm cho nó một. Làm cho nó bất biến nếu bạn có thể. Nếu bạn có hai đối tượng có con trỏ với nhau, hãy xóa một trong số chúng và tìm ra cách làm cho nó hoạt động. Tìm địa điểm để phân chia các lớp và chức năng. Nơi tốt nhất để phân chia một lớp có thể không phải lúc nào cũng khớp với bố cục vật lý của tệp. Hãy thử đảo ngược mối quan hệ cha mẹ / con cái của các lớp. Có lẽ bạn cần một lớp riêng để đọc và viết các tệp csv. Có lẽ bạn cần các lớp riêng biệt để xử lý tệp I / O và giao diện cho các lớp trên. Có rất nhiều điều để thử trước khi tuyên bố nó không thể thực hiện được.

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.