簡體   English   中英

一個單元應該如何測試hashCode-equals合約?

[英]How should one unit test the hashCode-equals contract?

簡而言之,hashCode契約,根據Java的object.hashCode():

  1. 除非影響equals()的內容發生變化,否則哈希碼不應更改
  2. equals()表示哈希碼是==

讓我們主要關注不可變數據對象 - 它們的信息在構造之后永遠不會改變,因此假定#1成立。 留下#2:問題只是確認等於隱含代碼==。

顯然,我們無法測試每個可想到的數據對象,除非該集合很小。 那么,編寫可能會遇到常見情況的單元測試的最佳方法是什么?

由於此類的實例是不可變的,因此構造此類對象的方法有限; 如果可能的話,這個單元測試應該涵蓋所有這些。 在我的腦海中,入口點是構造函數,反序列化和子類的構造函數(應該可以簡化為構造函數調用問題)。

[我打算通過研究來回答我自己的問題。 來自其他StackOverflowers的輸入是這個過程的一個受歡迎的安全機制。]

[這可能適用於其他OO語言,所以我添加了該標簽。]

EqualsVerifier是一個相對較新的開源項目,它在測試equals合同方面做得非常好。 它沒有來自GSBase的EqualsTester存在的問題 我肯定會推薦它。

我的建議是考慮為什么/如何不成立,然后寫一些針對這些情況的單元測試。

例如,假設您有一個自定義的Set類。 如果兩個集合包含相同的元素,則它們是相等的,但如果這些元素以不同的順序存儲,則兩個相等集合的基礎數據結構可能不同。 例如:

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

在這種情況下,集合中元素的順序可能會影響它們的散列,具體取決於您實現的散列算法。 所以這就是我要寫的那種測試,因為它測試的情況我知道一些散列算法可能會為我定義的兩個對象產生不同的結果。

您應該使用與您自己的自定義類相似的標准,無論是什么。

值得使用junit插件。 查看類EqualsHashCodeTestCase http://junit-addons.sourceforge.net/你可以擴展這個並實現createInstance和createNotEqualInstance,這將檢查equals和hashCode方法是否正確。

我會建議EqualsTester從GSBase。 它基本上是你想要的。 我有兩個(小)問題但是:

  • 構造函數完成所有工作,我認為這不是一個好習慣。
  • 當類A的實例等於類A的子類的實例時,它會失敗。這不一定違反equals合同。

[在撰寫本文時,發布了其他三個答案。]

重申一下,我的問題的目的是找到測試的標准情況,以確認hashCodeequals彼此一致。 我對這個問題的處理方法是想象程序員在編寫有問題的類時所采用的通用路徑,即不可變數據。 例如:

  1. 寫了equals()而沒有編寫hashCode() 這通常意味着將等式定義為表示兩個實例的字段相等。
  2. 寫了hashCode()而沒有寫equals() 這可能意味着程序員正在尋求更有效的散列算法。

在#2的情況下,我似乎不存在這個問題。 沒有其他實例使用equals() ,因此不需要其他實例來獲得相同的哈希碼。 在最壞的情況下,哈希算法可能會產生較差的哈希映射性能,這超出了本問題的范圍。

在#1的情況下,標准單元測試需要創建同一對象的兩個實例,並將相同的數據傳遞給構造函數,並驗證相同的哈希碼。 假陽性怎么樣? 有可能選擇恰好在一個不合理的算法上產生相同哈希碼的構造函數參數。 傾向於避免這些參數的單元測試將滿足這個問題的精神。 這里的捷徑是檢查equals()的源代碼,仔細思考,並根據它編寫測試,但在某些情況下這可能是必要的,也可能有常見的測試可以捕獲常見的問題 - 而且這樣的測試也是實現這個問題的精神。

例如,如果要測試的類(稱之為數據)有一個構造函數,需要一個String,並從該字符串是構建實例equals()得到那名實例equals() ,然后一個很好的測試將可能測試:

  • new Data("foo")
  • 另一個new Data("foo")

我們甚至可以檢查new Data(new String("foo"))的哈希代碼new Data(new String("foo")) ,以強制String不被實現,盡管這更可能產生正確的哈希代碼而不是Data.equals()產生正確的結果,在我看來。

伊萊Courtwright的回答是一種思維方式,打破基礎上的知識散列算法硬的例子equals規范。 特殊集合的例子很好,因為用戶自己的Collection有時會出現,並且很容易在哈希算法中出現問題。

這是我在測試中有多個斷言的唯一情況之一。 由於您需要測試equals方法,因此您還應該同時檢查hashCode方法。 因此,在每個equals方法測試用例中也檢查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());

當然,檢查一個好的hashCode是另一個練習。 老實說,我不打算進行雙重檢查以確保hashCode在同一次運行中沒有改變,通過在代碼審查中捕獲它並幫助開發人員理解為什么這不是一個好方法可以更好地處理這種問題編寫hashCode方法。

如果我有一個類Thing ,就像大多數其他人一樣,我會寫一個類ThingTest ,它包含該類的所有單元測試。 每個ThingTest都有一個方法

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

如果Thing類重寫hashCode並且等於它有一個方法

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

該方法負責檢查旨在保存在任何Thing對象之間的所有不變量。 它委托給它的ObjectTest方法負責檢查必須在任何一對對象之間保存的所有不變量。 由於equalshashCode是所有對象的方法,因此該方法檢查hashCodeequals是否一致。

然后我有一些測試方法創建Thing對象對,並將它們傳遞給成對checkInvariants方法。 我使用等價分區來決定哪些對值得測試。 我通常只在一個屬性中創建每個對,以及測試兩個等效對象的測試。

我有時也有一個3參數checkInvariants方法,雖然我發現在findinf缺陷中沒那么有用,所以我不經常這樣做

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM