Là gián điệp trên lớp thử nghiệm thực hành xấu?


12

Tôi đang làm việc trên một dự án nơi các cuộc gọi nội bộ của lớp là bình thường nhưng kết quả là các giá trị đơn giản gấp nhiều lần. Ví dụ ( không phải mã thật ):

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

Viết các bài kiểm tra đơn vị cho loại mã này thực sự khó vì tôi chỉ muốn kiểm tra hệ thống điều kiện chứ không phải thực hiện các điều kiện thực tế. (Tôi làm điều đó trong các thử nghiệm riêng biệt.) Trong thực tế sẽ tốt hơn nếu tôi vượt qua các chức năng thực hiện các điều kiện và trong các thử nghiệm tôi chỉ đơn giản cung cấp một số giả. Vấn đề với cách tiếp cận này là sự ồn ào: chúng tôi sử dụng thuốc generic rất nhiều .

Một giải pháp làm việc; tuy nhiên, là làm cho đối tượng được thử nghiệm trở thành một gián điệp và chế nhạo các cuộc gọi đến các chức năng bên trong.

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

Mối quan tâm ở đây là việc triển khai của SUT được thay đổi một cách hiệu quả và có thể có vấn đề khi giữ các thử nghiệm đồng bộ với việc triển khai. Điều này có đúng không? Có thực hành tốt nhất để tránh sự tàn phá của các cuộc gọi phương thức nội bộ này không?

Lưu ý rằng chúng ta đang nói về các phần của một thuật toán, do đó, việc chia nó ra một vài lớp có thể không phải là một quyết định mong muốn.

Câu trả lời:


14

Các bài kiểm tra đơn vị nên coi các lớp mà chúng kiểm tra là hộp đen. Điều duy nhất quan trọng là các phương thức công khai của nó hành xử theo cách nó được mong đợi. Làm thế nào lớp đạt được điều này thông qua các phương thức nội bộ và riêng tư không quan trọng.

Khi bạn cảm thấy rằng không thể tạo ra các bài kiểm tra có ý nghĩa theo cách này, đó là một dấu hiệu cho thấy các lớp học của bạn quá mạnh mẽ và làm quá nhiều. Bạn nên xem xét để chuyển một số chức năng của chúng sang các lớp riêng biệt có thể được kiểm tra riêng.


1
Tôi đã nắm bắt được ý tưởng thử nghiệm đơn vị từ lâu và đã viết một loạt chúng thành công. Nó chỉ lừa dối rằng một cái gì đó trông đơn giản trên giấy, nó trông tệ hơn trong mã, và cuối cùng tôi phải đối mặt với một cái gì đó có giao diện thực sự đơn giản nhưng yêu cầu tôi phải chế giễu một nửa thế giới xung quanh các đầu vào.
allprog

@allprog Khi bạn cần chế nhạo nhiều, có vẻ như bạn có quá nhiều phụ thuộc giữa các lớp. Bạn đã cố gắng giảm khớp nối giữa chúng?
Philipp

@allprog nếu bạn ở trong tình huống đó, thiết kế lớp là đáng trách.
itbruce

Đó là mô hình dữ liệu gây đau đầu. Nó phải tuân thủ các quy tắc ORM và nhiều yêu cầu khác. Với logic nghiệp vụ thuần túy và mã không trạng thái, việc kiểm tra đơn vị trở nên dễ dàng hơn nhiều.
allprog

3
Các bài kiểm tra đơn vị không nhất thiết phải xử lý SUT như hộp thư ngược. Đây là lý do tại sao chúng được gọi là kiểm tra đơn vị. Với việc chế giễu các phụ thuộc mà tôi có thể ảnh hưởng đến môi trường và để biết những gì tôi phải chế giễu, tôi cũng phải biết một số nội bộ. Nhưng tất nhiên điều này không có nghĩa là SUT nên được thay đổi theo bất kỳ cách nào. Gián điệp, tuy nhiên, cho phép thực hiện một số thay đổi.
allprog

4

Nếu cả hai findError()checkFirstCondition()vv là các phương thức công khai của lớp của bạn, thì đó thực sự findError()là một mặt tiền cho chức năng đã có sẵn từ cùng một API. Không có gì sai với điều đó, nhưng điều đó có nghĩa là bạn phải viết các bài kiểm tra cho nó rất giống với các bài kiểm tra đã có. Sự trùng lặp này chỉ đơn giản phản ánh sự trùng lặp trong giao diện công cộng của bạn. Đó không phải là lý do để đối xử với phương pháp này khác với những người khác.


Các phương thức bên trong được công khai chỉ vì chúng cần có thể kiểm tra được và tôi không muốn phân lớp SUT hoặc bao gồm các kiểm tra đơn vị trong lớp SUT như một lớp bên trong tĩnh. Nhưng tôi nhận được quan điểm của bạn. Tuy nhiên, tôi không thể tìm thấy các dòng hướng dẫn tốt để tránh các loại tình huống này. Các hướng dẫn luôn bị kẹt ở mức cơ bản không liên quan gì đến phần mềm thực sự. Mặt khác, lý do gián điệp là chính xác để tránh trùng lặp mã kiểm tra và làm cho đơn vị kiểm tra có phạm vi.
allprog

3
Tôi không đồng ý rằng các phương thức của người trợ giúp cần được công khai để kiểm tra đơn vị thích hợp. Nếu hợp đồng của một phương thức nói rằng nó kiểm tra các điều kiện khác nhau, thì không có gì sai khi viết một số thử nghiệm đối với cùng một phương thức công khai, một thử nghiệm cho mỗi "hợp đồng phụ". Quan điểm của các thử nghiệm đơn vị là để đạt được phạm vi bảo hiểm của tất cả các mã, không phải để đạt được độ bao phủ bề ngoài của các phương thức công khai thông qua sự tương ứng thử nghiệm phương pháp 1: 1.
Kilian Foth

Chỉ sử dụng API công khai để kiểm tra phức tạp hơn nhiều lần so với kiểm tra từng phần nội bộ. Tôi không tranh luận, tôi nhận ra rằng cách tiếp cận này không phải là tốt nhất và nó có tầm nhìn xa mà câu hỏi của tôi cho thấy. Vấn đề lớn nhất là các hàm không thể kết hợp được trong Java và cách giải quyết là vô cùng ngắn gọn. Nhưng dường như không có giải pháp nào khác để thử nghiệm đơn vị thực sự.
allprog

3

Kiểm tra đơn vị nên kiểm tra hợp đồng; đó là điều quan trọng duy nhất đối với họ Kiểm tra bất cứ điều gì không phải là một phần của hợp đồng không chỉ là lãng phí thời gian, mà còn là một nguồn gây ra lỗi. Bất cứ khi nào bạn thấy một nhà phát triển thay đổi các bài kiểm tra khi anh ta thay đổi một chi tiết thực hiện, tiếng chuông báo thức sẽ vang lên; nhà phát triển đó có thể (dù cố ý hay không) che giấu lỗi lầm của mình. Cố tình kiểm tra chi tiết thực hiện buộc thói quen xấu này, làm cho nhiều khả năng lỗi sẽ bị che giấu.

Các cuộc gọi nội bộ là một chi tiết triển khai và chỉ nên được quan tâm trong việc đo lường hiệu suất . Mà thường không phải là công việc của các bài kiểm tra đơn vị.


Âm thanh tuyệt vời. Nhưng trong thực tế, "chuỗi" tôi phải nhập và gọi nó là mã trong một ngôn ngữ biết rất ít về các hàm. Về lý thuyết tôi có thể dễ dàng mô tả một vấn đề và thực hiện thay thế ở đây và ở đó để đơn giản hóa nó. Trong mã tôi phải thêm rất nhiều tiếng ồn cú pháp để đạt được tính linh hoạt này khiến tôi không sử dụng nó. Nếu phương thức achứa lệnh gọi phương thức btrong cùng một lớp, thì các kiểm tra aphải bao gồm các kiểm tra b. Và không có cách nào để thay đổi điều này miễn blà không được chuyển thành atham số Nhưng tôi không có giải pháp nào khác.
allprog

1
Nếu blà một phần của giao diện công cộng, dù sao thì nó cũng nên được kiểm tra. Nếu không, nó không cần phải được kiểm tra. Nếu bạn công khai nó chỉ vì bạn muốn kiểm tra nó, bạn đã làm sai.
itbruce

Xem bình luận của tôi để trả lời của @ Philip. Tôi chưa đề cập đến nhưng mô hình dữ liệu là nguồn gốc của cái ác. Tinh khiết, không quốc tịch là một miếng bánh.
allprog

2

Đầu tiên, tôi tự hỏi có gì khó kiểm tra về chức năng ví dụ bạn đã viết? Theo như tôi có thể thấy, bạn chỉ cần chuyển vào nhiều đầu vào khác nhau và kiểm tra để đảm bảo giá trị boolean chính xác được trả về. Tôi đang thiếu gì?

Đối với các điệp viên, loại thử nghiệm được gọi là "hộp trắng" sử dụng gián điệp và giả là các đơn đặt hàng lớn hơn để viết, không chỉ bởi vì có rất nhiều mã kiểm tra để viết, mà bất cứ khi nào thực hiện đã thay đổi, bạn cũng phải thay đổi các bài kiểm tra (ngay cả khi giao diện vẫn giữ nguyên). Và loại thử nghiệm này cũng kém tin cậy hơn thử nghiệm hộp đen, bởi vì bạn cần đảm bảo rằng tất cả mã kiểm tra bổ sung đó là chính xác và trong khi bạn có thể tin tưởng rằng các thử nghiệm đơn vị hộp đen sẽ thất bại nếu chúng không khớp với giao diện , bạn không thể tin tưởng về việc lạm dụng mã giả vì đôi khi thử nghiệm thậm chí không kiểm tra nhiều mã thực - chỉ có giả. Nếu các giả định không chính xác, khả năng là các thử nghiệm của bạn sẽ thành công, nhưng mã của bạn vẫn bị hỏng.

Bất cứ ai có kinh nghiệm về kiểm tra hộp trắng đều có thể nói với bạn rằng họ rất khó viết và duy trì. Cùng với thực tế là chúng kém tin cậy hơn, thử nghiệm hộp trắng đơn giản là kém hơn nhiều trong hầu hết các trường hợp.


Cảm ơn đã lưu ý. Hàm ví dụ là các đơn đặt hàng có cường độ đơn giản hơn bất kỳ thứ gì bạn phải viết trong một thuật toán phức tạp. Trên thực tế, câu hỏi hóa ra giống như: có vấn đề gì khi kiểm tra thuật toán với các điệp viên ở một số phần. Đây không phải là mã trạng thái, tất cả các trạng thái được tách ra thành các đối số đầu vào. Vấn đề là tôi muốn kiểm tra hàm phức tạp trong ví dụ mà không phải cung cấp các tham số lành mạnh cho các hàm phụ.
allprog

Với sự khởi đầu của lập trình chức năng trong Java 8, điều này đã trở nên thanh lịch hơn một chút nhưng vẫn giữ chức năng trong một lớp duy nhất có thể là lựa chọn tốt hơn trong trường hợp thuật toán thay vì trích xuất các phần khác nhau (một mình không hữu ích) thành "sử dụng một lần" các lớp học chỉ vì khả năng kiểm tra. Về mặt này, các điệp viên làm tương tự như giả, nhưng không cần phải làm nổ mã trực quan. Trên thực tế, mã thiết lập tương tự được sử dụng như với giả. Tôi thích tránh xa các thái cực, mọi loại thử nghiệm có thể phù hợp ở những nơi cụ thể. Kiểm tra bằng cách nào đó tốt hơn nhiều so với không có cách nào. :)
allprog

"Tôi muốn kiểm tra hàm phức tạp .. mà không phải cung cấp các tham số lành mạnh cho các hàm phụ" - Tôi không hiểu ý của bạn ở đó. Những chức năng phụ nào? Bạn đang nói về các chức năng nội bộ đang được sử dụng bởi 'chức năng phức tạp'?
BT

Đó là những gì gián điệp có ích cho trường hợp của tôi. Các chức năng nội bộ khá phức tạp để kiểm soát. Không phải vì mã mà vì họ thực hiện một cái gì đó phức tạp về mặt logic. Di chuyển công cụ trong một lớp khác là một tùy chọn tự nhiên nhưng chỉ riêng các chức năng đó không hữu ích. Do đó, giữ cho lớp cùng nhau và kiểm soát nó bằng chức năng gián điệp hóa ra là một lựa chọn tốt hơn. Đã làm việc hoàn hảo trong gần một năm, và có thể dễ dàng chịu được những thay đổi mô hình. Tôi đã không sử dụng mô hình này kể từ đó, chỉ nghĩ rằng tốt để đề cập rằng nó khả thi trong một số trường hợp.
allprog

@allprog "logic phức tạp" - Nếu nó phức tạp, bạn cần các bài kiểm tra phức tạp. Không có cách nào khác. Các gián điệp sẽ làm cho nó khó khăn và phức tạp hơn đối với bạn. Bạn nên tạo các chức năng phụ dễ hiểu mà bạn có thể tự kiểm tra, thay vì sử dụng gián điệp để kiểm tra hành vi đặc biệt của chúng bên trong chức năng khác.
BT
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.