2013-02-20 15 views
7

Tôi đang cố gắng tìm hiểu thêm về JUnit và TDD, nhưng tôi đang gặp phải một số vấn đề với việc ghép nối giữa các trường hợp thử nghiệm.Giảm Khớp nối giữa các Trường hợp Kiểm tra

Khi tôi viết một trường hợp thử nghiệm cho một API kiểu dữ liệu cụ thể, giả sử là Deque<T>, làm cách nào tôi có thể giới hạn khớp nối giữa các trường hợp thử nghiệm? Ví dụ, nếu tôi đang viết một trường hợp thử nghiệm đối với phương pháp insertFirst(T item), có vẻ như đơn giản để giả định rằng tôi sẽ có thể khẳng định hai điều sau khi gọi phương thức trên một đối tượng khởi tạo đúng cách:

  1. Kích thước của Deque đối tượng phải được tăng thêm
  2. Nếu sau đó tôi gọi phương thức T removeFirst() tương ứng, nó sẽ trả về tham chiếu đến đối tượng tôi đã chèn với lệnh gọi ban đầu.

Tuy nhiên, điều này tạo ra sự ghép nối không mong muốn giữa ít nhất hai trường hợp thử nghiệm, trong đó một trường hợp thử nghiệm phụ thuộc vào việc triển khai đúng phương pháp API khác. Ví dụ, để cho trường hợp thử nghiệm này được thông qua, tôi sẽ cần thực hiện chính xác để kiểm tra số lượng các mục trong Deque và cũng để loại bỏ các mục. Nếu thử nghiệm của tôi cho một trong những phương pháp đó là không chính xác hoặc không đầy đủ vì bất kỳ lý do gì, thì thử nghiệm của tôi cho phương pháp insertFirst sẽ tự động bị nghi ngờ.

Thực tiễn tốt nhất để tránh trường hợp này là gì? Cách tiếp cận của tôi để viết các trường hợp thử nghiệm sai theo một cách nào đó?

Trả lời

12

Khi viết bài kiểm tra cho một phương pháp, bạn để giả định rằng phần còn lại của lớp đang hoạt động chính xác. Nếu bạn không đưa ra giả định này, kết luận duy nhất sẽ là một bài kiểm tra lớn mỗi bài kiểm tra. Và đó không phải là những gì chúng tôi làm.

Bạn có thể giả định rằng các phần khác của lớp hoạt động chính xác, bởi vì sẽ có các bài kiểm tra cho các phần khác, đảm bảo tính chính xác của chúng.
Nếu một phần không hoạt động chính xác, kiểm tra sẽ không thành công, cho bạn biết rằng có điều gì đó không chính xác.
Ngay sau khi kiểm tra bộ thử nghiệm của bạn không thành công, có lỗi bạn phải khắc phục. Bạn không còn có thể đưa ra bất kỳ giả định nào.

Ví dụ:

Bạn có thực hiện danh sách đơn giản chỉ với ba phương pháp:

  1. chèn
  2. loại bỏ
  3. đếm

Bạn có ba bài kiểm tra:

  1. thử nghiệm cho insert:
    • tạo thể hiện của danh sách (Sắp xếp)
    • insert mục (Luật)
    • kiểm count bằng 1 (Khẳng định)
  2. Thử nghiệm cho remove:
    • tạo thể hiện của danh sách và insert mục (Sắp xếp)
    • remove mục (Luật)
    • kiểm count bằng 0 (Khẳng định)
  3. thử nghiệm cho count:
    • tạo phiên bản danh sách và insert n mục (Sắp xếp)
    • lấy count (Luật)
    • kiểm count bằng n (Khẳng định)

Bây giờ, nếu bất kỳ của các cuộc thử nghiệm trên thất bại, bạn có thể không đảm bảo tính chính xác của một thành viên trong lớp học của bạn:

  • I f kiểm tra đầu tiên thất bại, thứ ba cũng sẽ thất bại. Người thứ hai sẽ vượt qua, nhưng không thực sự thử nghiệm remove, bởi vì không có gì để xóa.
  • Nếu thử nghiệm thứ hai không thành công, hai thử nghiệm còn lại vẫn sẽ vượt qua. Tuy nhiên, bạn không thể chắc chắn rằng insertcount đang hoạt động chính xác, vì thử nghiệm thứ hai sẽ thất bại nếu bất kỳ thành viên nào trong số ba thành viên không hoạt động chính xác.
  • Nếu thử nghiệm thứ ba thất bại, hai trường hợp khác có nhiều khả năng sẽ không thành công.

Kiểm tra không thành công cho bạn biết điều gì đó mặc dù:
Tùy thuộc vào các thử nghiệm không thành công, bạn thường có thể khấu trừ nơi lỗi phải xảy ra.
Ví dụ: Nếu chỉ thử nghiệm thứ hai thất bại nhưng không phải là lần đầu tiên hoặc thứ ba, lỗi có nhiều khả năng nhất là ở phương pháp remove.

+1

Tôi hiểu những gì bạn đang nói, nhưng điều này không tạo ra sự phụ thuộc vòng tròn của các phương pháp? Để kiểm tra chèn, tôi phải sử dụng loại bỏ, và để kiểm tra loại bỏ, tôi đã sử dụng chèn? Điều này có vẻ sai. Nó có thể là tôi chỉ không có đủ kinh nghiệm với những ý tưởng và tôi sẽ sớm cảm thấy thoải mái với việc không thoải mái. Cảm ơn bạn đã làm rõ. – crlane

+1

@crlane: Vâng, nó tạo ra một số loại phụ thuộc vòng tròn. Đó là lý do tại sao bạn không thể nói * bất cứ điều gì * về lớp học của bạn nếu ngay cả một bài kiểm tra thất bại. Chỉ khi các bài kiểm tra * tất cả * trôi qua, bạn biết rằng mọi thứ hoạt động như mong đợi. –

+0

@crlane: Vui lòng xem câu trả lời được cập nhật để biết ví dụ cụ thể. –

4

Nói chung là hiệu quả hơn khi nghĩ về các bài kiểm tra đơn vị như kiểm tra các tính năng cụ thể thay vì các phương pháp cụ thể. Bất kỳ thử nghiệm nào cũng sẽ kiểm tra xem một số phương thức hoạt động chính xác để thực hiện tính năng đó là đối tượng của thử nghiệm và mẫu thất bại trong một bộ kiểm thử được thiết kế tốt sẽ cho bạn biết phương pháp nào đã phá vỡ khá nhanh.

Một bộ sưu tập tốt các xét nghiệm có xu hướng giảm tự nhiên khi thực hiện TDD; đó là một trong những điều làm cho kỹ thuật trở nên mạnh mẽ. Nếu tôi đang viết một Deque, các bài kiểm tra tôi viết sẽ có xu hướng như sau, thường trình bày theo thứ tự này.

  1. empty_Deque_isEmpty - thực hiện isEmpty để luôn trở true
  2. non_empty_Deque_isntEmpty - thực hiện insertFirst để làm isEmpty dụ biến sai
  3. re_emptied_Deque_isEmpty - thay đổi Ví dụ biến được sử dụng bởi isEmpty là một con số đó phản ứng với insertFirstremoveFirst
  4. is_empty_Deque_size_correct - triển khai size để luôn trả lại 0
  5. is_nonempty_Deque_size_correct - thêm biến mẫu để theo dõi kích thước; nhận ra nó đang làm điều tương tự cần thiết bởi isEmpty; Refactor
  6. is_re_emptied_Deque_size_correct - có kiểm tra chỉ cần vượt qua vì những gì chúng tôi đã làm để làm cho 5. xảy ra
  7. does_removing_from_empty_Deque_throw-removeFirst nhu cầu để kiểm tra size trước khi làm bất cứ điều gì khác
  8. is_inserted_item_returned - insertFirstremoveFirst tại cư một T biến mẫu
  9. is_inserted_item_returned_from_end - thêm removeLast là bản sao của removeFirst; refactor
  10. is_rear_inserted_item_returned - thêm insertLast sao chép insertFirst; refactor
  11. are_all_inserted_items_returned - thay đổi insertFirstremoveFirst để hành động theo số SomeKindOfCollection<T>; làm cho một điểm không kiểm tra thứ tự truy xuất
  12. does_removeFirst_retrieve_items_in_correct_order - chèn hai thứ, hãy đảm bảo thứ hai được trả về removeFirst. Có thể đã đúng.
  13. does_removeLast_retrieve_items_in_correct_order - ditto cho removeLast, ngoại trừ khá chắc chắn chưa được chuyển.

Đó là toàn bộ các thử nghiệm, nhưng khi bạn xem qua chúng, bạn sẽ thấy mẫu. Không có thử nghiệm nào trong số này thực sự là "thử nghiệm cho count" hoặc "thử nghiệm cho removeFirst". Nhưng vào thời điểm chúng ta trải qua, toàn bộ giao diện của lớp đang được thực hiện và tất cả các internals cần thiết cho giao diện đó đã được phát triển.Một số thử nghiệm phụ thuộc vào nhiều hơn một phương pháp và nếu phương pháp đó sẽ thất bại, tất cả chúng sẽ bị hỏng. Nhưng mô hình phá vỡ sẽ có xu hướng rất hữu ích trong việc xác định vị trí của lỗi. Cũng thú vị là có bao nhiêu thử nghiệm mà chúng ta có thể thực hiện mà không cần phải cam kết thực sự có một bộ sưu tập trong đối tượng, điều này gợi ý rằng bộ kiểm thử đó có thể được đưa vào một bộ thử nghiệm chung chung hơn. hữu ích khi phát triển PriorityQueue.

+0

+1 Tôi hoàn toàn đồng ý về "thử nghiệm các tính năng cụ thể thay vì các phương pháp cụ thể". Tôi mặc dù về việc đưa nó vào câu trả lời của tôi là tốt nhưng tôi đã có cảm giác nó sẽ áp đảo OP. Vui vì bạn đã mang nó lên! –