2008-10-09 33 views
62

Tóm lại, hợp đồng hashCode, theo object.hashCode Java():Làm thế nào một đơn vị nên kiểm tra hợp đồng hashCode-equals?

  1. Mã băm không nên thay đổi trừ khi một cái gì đó ảnh hưởng đến equals() thay đổi
  2. equals() ngụ ý mã băm là = =

Giả sử quan tâm chủ yếu trong các đối tượng dữ liệu không thay đổi - thông tin của chúng không bao giờ thay đổi sau khi chúng được tạo, vì vậy # 1 được giả định giữ. Rằng # 2: vấn đề chỉ đơn giản là một trong những xác nhận rằng bằng hàm ngụ ý mã băm ==.

Rõ ràng, chúng tôi không thể kiểm tra mọi đối tượng dữ liệu có thể tưởng tượng trừ khi tập hợp đó nhỏ một cách đáng kể. Vì vậy, cách tốt nhất để viết một bài kiểm tra đơn vị có khả năng bắt các trường hợp phổ biến là gì?

Vì các trường hợp của lớp này là không thay đổi, có những cách hạn chế để xây dựng một đối tượng như vậy; kiểm tra đơn vị này nên bao gồm tất cả chúng nếu có thể. Trên đỉnh đầu của tôi, các điểm vào là các hàm tạo, deserialization và các hàm tạo của các lớp con (mà nên được giảm bớt cho vấn đề gọi hàm dựng).

[Tôi sẽ cố gắng trả lời câu hỏi của riêng mình thông qua nghiên cứu. Đầu vào từ stackoverflowers khác là một cơ chế an toàn hoan nghênh quá trình này.]

[Điều này có thể áp dụng đối với ngôn ngữ OO khác, vì vậy tôi thêm thẻ đó.]

Trả lời

9

Lời khuyên của tôi sẽ phải suy nghĩ về việc tại sao/làm thế nào điều này có thể không bao giờ đúng, và sau đó viết một số xét nghiệm đơn vị mà mục tiêu những tình huống.

Ví dụ: giả sử bạn có lớp học Set tùy chỉnh. Hai bộ là bằng nhau nếu chúng chứa các phần tử giống nhau, nhưng có thể cho các cấu trúc dữ liệu cơ bản của hai bộ bằng nhau khác nhau nếu các phần tử đó được lưu trữ theo thứ tự khác. Ví dụ:

MySet s1 = new MySet(new String[]{"Hello", "World"}); 
MySet s2 = new MySet(new String[]{"World", "Hello"}); 
assertEquals(s1, s2); 
assertTrue(s1.hashCode()==s2.hashCode()); 

Trong trường hợp này, thứ tự của các phần tử trong tập hợp có thể ảnh hưởng đến thuật toán băm của bạn. Vì vậy, đây là loại thử nghiệm tôi muốn viết, vì nó kiểm tra trường hợp mà tôi biết nó sẽ có thể cho một số thuật toán băm để tạo ra các kết quả khác nhau cho hai đối tượng mà tôi đã xác định là bằng nhau.

Bạn nên sử dụng một tiêu chuẩn tương tự với lớp tùy chỉnh của riêng bạn, bất kể đó là gì.

1

Đây là một trong những trường hợp duy nhất tôi có nhiều xác nhận trong thử nghiệm. Vì bạn cần kiểm tra phương thức equals, bạn cũng nên kiểm tra phương thức hashCode cùng một lúc. Vì vậy, trên mỗi trường hợp thử nghiệm phương thức equals của bạn cũng kiểm tra hợp đồng hashCode.

A one = new A(...); 
A two = new A(...); 
assertEquals("These should be equal", one, two); 
int oneCode = one.hashCode(); 
assertEquals("HashCodes should be equal", oneCode, two.hashCode()); 
assertEquals("HashCode should not change", oneCode, one.hashCode()); 

Và tất nhiên kiểm tra mã băm tốt là một bài tập khác. Thành thật mà nói, tôi không bận tâm kiểm tra lại để đảm bảo hashCode không thay đổi trong cùng một lần chạy, loại vấn đề đó được xử lý tốt hơn bằng cách bắt nó trong một đánh giá mã và giúp nhà phát triển hiểu tại sao đó không phải là cách tốt để viết các phương thức hashCode.

4

Tôi muốn giới thiệu số EqualsTester từ GSBase. Nó về cơ bản những gì bạn muốn.Tôi có hai (nhỏ) vấn đề với nó mặc dù:

  • Nhà xây dựng thực hiện tất cả công việc mà tôi không cho là thực hành tốt.
  • Nó không thành công khi một thể hiện của lớp A bằng với một thể hiện của một lớp con của lớp A. Điều này không nhất thiết phải vi phạm hợp đồng bằng.
2

[Tại thời điểm viết bài này, ba câu trả lời khác được đăng.]

Nói cách khác, mục đích của câu hỏi của tôi là tìm những trường hợp tiêu chuẩn của các bài kiểm tra để xác nhận rằng hashCodeequals đồng ý với nhau khác. Cách tiếp cận của tôi đối với câu hỏi này là tưởng tượng các đường dẫn phổ biến được thực hiện bởi các lập trình viên khi viết các lớp trong câu hỏi, cụ thể là, dữ liệu bất biến. Ví dụ:

  1. Viết equals() mà không cần viết hashCode(). Điều này thường có nghĩa là bình đẳng được định nghĩa là bình đẳng của các trường của hai trường hợp.
  2. Đã viết hashCode() mà không cần viết equals(). Điều này có nghĩa là lập trình viên đang tìm kiếm một thuật toán băm hiệu quả hơn.

Trong trường hợp # 2, vấn đề có vẻ không tồn tại đối với tôi. Không có trường hợp bổ sung nào được thực hiện equals(), do đó không yêu cầu thêm phiên bản nào để có mã băm bằng nhau. Tệ nhất, thuật toán băm có thể mang lại hiệu năng kém hơn cho các bản đồ băm, nằm ngoài phạm vi của câu hỏi này.

Trong trường hợp # 1, kiểm tra đơn vị tiêu chuẩn đòi hỏi phải tạo hai trường hợp của cùng một đối tượng có cùng dữ liệu được truyền cho hàm tạo và xác minh mã băm bằng nhau. Điều gì về dương tính giả? Có thể chọn các thông số của hàm tạo chỉ xảy ra để tạo ra các mã băm bằng nhau trên một thuật toán không có gì đó. Một bài kiểm tra đơn vị có xu hướng tránh các tham số như vậy sẽ đáp ứng được tinh thần của câu hỏi này. Phím tắt ở đây là kiểm tra mã nguồn cho equals(), suy nghĩ kỹ và viết thử dựa trên điều đó, nhưng trong khi điều này có thể cần thiết trong một số trường hợp, cũng có thể có các thử nghiệm phổ biến gặp sự cố thường gặp - tinh thần của câu hỏi này.

Ví dụ, nếu lớp được kiểm tra (gọi nó là dữ liệu) có một nhà xây dựng mà phải mất một String, và các trường hợp xây dựng từ Strings mà equals() mang lại những trường hợp đó là equals(), sau đó một thử nghiệm tốt có lẽ sẽ thử nghiệm:

  • new Data("foo")
  • khác new Data("foo")

Chúng tôi thậm chí có thể kiểm tra mã băm cho new Data(new String("foo")), để buộc các String để không được tập trung, mặc dù đó là nhiều khả năng để mang lại một mã băm chính xác hơn Data.equals() là để mang lại một kết quả chính xác, theo ý kiến ​​của tôi.

Câu trả lời của Eli Courtwright là một ví dụ về cách suy nghĩ về cách phá vỡ thuật toán băm dựa trên kiến ​​thức về đặc điểm equals. Ví dụ về một bộ sưu tập đặc biệt là một bộ sưu tập tốt, vì người dùng thực hiện Collection s ở lần lượt, và khá dễ bị mắc kẹt trong thuật toán băm.

6

Đó là giá trị sử dụng tiện ích junit cho việc này. Kiểm tra các lớp EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ bạn có thể mở rộng này và thực hiện createInstance và createNotEqualInstance, điều này sẽ kiểm tra các phương thức equals và hashCode là chính xác.

60

EqualsVerifier là một dự án mã nguồn mở tương đối mới và thực hiện công việc rất tốt khi kiểm tra hợp đồng bằng. Nó không có issues EqualsTester từ GSBase. Tôi chắc chắn sẽ giới thiệu nó.

+7

Chỉ _using_ EqualsVerifier dạy tôi điều gì đó về Java! – David

+0

Tôi có bị điên không? EqualsVerifier dường như không kiểm tra ngữ nghĩa Value Object ở tất cả! Nó cho rằng lớp của tôi thực hiện đúng bằng(), nhưng lớp của tôi sử dụng hành vi "==" mặc định. Làm sao có thể?! Tôi phải thiếu một cái gì đó đơn giản. –

+0

Aha! Nếu tôi không ghi đè bằng(), thì EqualsVerifier giả định rằng mọi thứ đều ổn chứ !? Đó không phải là điều tôi mong đợi. Tôi sẽ mong đợi hành vi tương tự cho không trọng bằng() như cho trọng bằng() để làm chính xác cùng một điều super.equals(). Kỳ dị. –

0

Nếu tôi có một lớp học Thing, như hầu hết những người khác, tôi viết một lớp học ThingTest, chứa tất cả các bài kiểm tra đơn vị cho lớp đó. Mỗi ThingTest có một phương pháp

public static void checkInvariants(final Thing thing) { 
    ... 
} 

và nếu lớp Thing đè hashCode và equals nó có một phương pháp

public static void checkInvariants(final Thing thing1, Thing thing2) { 
    ObjectTest.checkInvariants(thing1, thing2); 
    ... invariants that are specific to Thing 
} 

Đó là phương pháp có trách nhiệm kiểm tra tất cả bất biến được thiết kế để giữ giữa bất kỳ cặp của các đối tượng Thing. Phương thức ObjectTest mà nó ủy quyền chịu trách nhiệm kiểm tra tất cả các bất biến phải giữ giữa bất kỳ cặp đối tượng nào. Vì equalshashCode là các phương pháp của tất cả các đối tượng, phương pháp đó kiểm tra rằng hashCodeequals nhất quán.

Sau đó tôi có một số phương pháp thử tạo cặp đối tượng Thing và chuyển chúng sang phương thức pair2 checkInvariants. Tôi sử dụng phân vùng tương đương để quyết định những cặp nào đáng được thử nghiệm. Tôi thường tạo mỗi cặp khác nhau chỉ trong một thuộc tính, cộng với phép thử kiểm tra hai đối tượng tương đương.

Tôi cũng thỉnh thoảng có một phương pháp 3 lập luận checkInvariants, mặc dù tôi nhận thấy rằng ít hữu ích trong các khuyết tật findinf, vì vậy tôi không làm điều này thường

+0

Điều này tương tự như những gì người đăng khác đề cập ở đây: http://stackoverflow.com/a/190989/545127 – Raedwald

Các vấn đề liên quan