在沒有 equals() 方法的情況下斷言兩個類的相等性
一、概述
有時,我們沒有能力重寫類中的equals()
方法。儘管如此,我們仍然希望將一個對象與另一個對象進行比較,以檢查它們是否相同。
在本教程中,我們將學習幾種在不使用equals()
方法的情況下測試兩個對像是否相等的方法。
2.示例類
在我們深入之前,讓我們創建我們將通過示例使用的類。我們將使用Person
和Address
類:
public class Person {
private Long id;
private String firstName;
private String lastName;
private Address address;
// getters and setters
}
public class Address {
private Long id;
private String city;
private String street;
private String country;
// getters and setters
}
我們沒有覆蓋類中的equals()
方法。因此,在確定相等性時將執行Object
類中給出的默認實現。換句話說, Java在檢查相等時會檢查兩個引用是否指向同一個對象。
3.使用AssertJ
AssertJ 庫提供了一種使用遞歸比較來比較對象的方法。使用內省,它確定應該比較哪些字段和值。
首先,要使用 AssertJ 庫,讓我們在pom.xml
中添加assertj-core依賴項:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.21.0</version>
<scope>test</scope>
</dependency>
要檢查兩個Person
實例中的字段是否包含相同的值,我們將在調用isEqualTo()
方法之前使用usingRecursiveComparison()
方法:
Person expected = new Person(1L, "Jane", "Doe");
Person actual = new Person(1L, "Jane", "Doe");
assertThat(actual).usingRecursiveComparison().isEqualTo(expected);
此外,該算法獲取實際對象的字段,然後將它們與預期對象的相應字段進行比較。但是,比較不能以對稱方式進行。預期的對象可以有比實際對象更多的字段。
此外,我們可以使用ignoringFields()
方法忽略某個字段:
Person expected = new Person(1L, "Jane", "Doe");
Person actual = new Person(2L, "Jane", "Doe");
assertThat(actual)
.usingRecursiveComparison()
.ignoringFields("id")
.isEqualTo(expected);
此外,當我們想要比較複雜的對象時,它可以有效地工作:
Person expected = new Person(1L, "Jane", "Doe");
Address address1 = new Address(1L, "New York", "Sesame Street", "United States");
expected.setAddress(address1);
Person actual = new Person(1L, "Jane", "Doe");
Address address2 = new Address(1L, "New York", "Sesame Street", "United States");
actual.setAddress(address2);
assertThat(actual)
.usingRecursiveComparison()
.isEqualTo(expected);
4. 使用 Hamcrest
Hamcrest 庫使用反射來檢查兩個對像是否包含相同的屬性。此外,它創建一個匹配器來檢查實際對像是否包含與預期對象相同的值。
首先,讓我們在pom.xml
中添加Hamcrest依賴項:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
現在,讓我們調用sameProperyValuesAs()
方法並傳遞預期的對象:
Person expected = new Person(1L, "Jane", "Doe");
Person actual = new Person(1L, "Jane", "Doe");
MatcherAssert.assertThat(actual, samePropertyValuesAs(expected));
與前面的示例類似,我們可以傳遞要忽略的字段的名稱。它們將從預期和實際對像中刪除。
然而,在幕後,Hamcrest 使用反射從某些字段中獲取值。檢查是否相等時,將在每個字段上調用equals()
方法。
也就是說,如果我們使用複雜對象,上面的代碼將不起作用,因為我們也沒有覆蓋Address
類中的equals()
方法。因此,它將檢查兩個Address
引用是否指向內存中的同一個對象。因此,斷言將失敗。
如果我們想比較複雜的對象,我們需要分別比較它們:
Person expected = new Person(1L, "Jane", "Doe");
Address address1 = new Address(1L, "New York", "Sesame Street", "United States");
expected.setAddress(address1);
Person actual = new Person(1L, "Jane", "Doe");
Address address2 = new Address(1L, "New York", "Sesame Street", "United States");
actual.setAddress(address2);
MatcherAssert.assertThat(actual, samePropertyValuesAs(expected, "address"));
MatcherAssert.assertThat(actual.getAddress(), samePropertyValuesAs(expected.getAddress()));
在這裡,我們首先從第一個斷言中排除了address
字段,然後在第二個斷言中對其進行了比較。
5. 使用 Apache Commons Lang3
現在,讓我們看看如何使用 Apache Commons 庫檢查是否相等。
我們將在pom.xml
中添加Apache Commons Lang3依賴項:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
<scope>test</scope>
</dependency>
5.1. ReflectionToStringBuilder
類
Apache Commons 提供的類之一是ReflectionToStringBuilder
類。它允許我們通過反映對象的字段和值來生成對象的字符串表示形式。
通過比較兩個對象的字符串表示,我們可以斷言它們相等而不需要使用equals()
方法:
Person expected = new Person(1L, "Jane", "Doe");
Person actual = new Person(1L, "Jane", "Doe");
assertThat(ReflectionToStringBuilder.toString(actual, ToStringStyle.SHORT_PREFIX_STYLE))
.isEqualTo(ReflectionToStringBuilder.toString(expected, ToStringStyle.SHORT_PREFIX_STYLE));
但是,我們仍然需要在我們的類中覆蓋toString()
方法。
5.2. EqualsBuilder
類
或者,我們可以使用EqualsBuilder
類:
Person expected = new Person(1L, "Jane", "Doe");
Person actual = new Person(1L, "Jane", "Doe");
assertTrue(EqualsBuilder.reflectionEquals(expected, actual));
它使用 Java 反射 API 來比較兩個對象的字段。請務必注意, reflectionEquals()
方法使用淺層相等性檢查。
因此,當比較兩個複雜對象時,我們需要忽略這些字段並分別進行比較
Person expected = new Person(1L, "Jane", "Doe");
Address address1 = new Address(1L, "New York", "Sesame Street", "United States");
expected.setAddress(address1);
Person actual = new Person(1L, "Jane", "Doe");
Address address2 = new Address(1L, "New York", "Sesame Street", "United States");
actual.setAddress(address2);
assertTrue(EqualsBuilder.reflectionEquals(expected, actual, "address"));
assertTrue(EqualsBuilder.reflectionEquals(expected.getAddress(), actual.getAddress()));
6. 使用 Mockito
我們斷言兩個實例相等的另一種方法是使用 Mockito。
我們需要mockito-core依賴項:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
現在,我們可以使用 Mockito 的ReflectionEquals
類:
Person expected = new Person(1L, "Jane", "Doe");
Person actual = new Person(1L, "Jane", "Doe");
assertTrue(new ReflectionEquals(expected).matches(actual));
此外,在檢查相等性時將調用 Apache Commons 庫中的EqualsBuilder
。
再一次,我們需要使用與EqualsBuilder
相同的補丁來比較複雜對象:
Person expected = new Person(1L, "Jane", "Doe");
Address address1 = new Address(1L, "New York", "Sesame Street", "United States");
expected.setAddress(address1);
Person actual = new Person(1L, "Jane", "Doe");
Address address2 = new Address(1L, "New York", "Sesame Street", "United States");
actual.setAddress(address2);
assertTrue(new ReflectionEquals(expected, "address").matches(actual));
assertTrue(new ReflectionEquals(expected.getAddress()).matches(actual.getAddress()));
七、結論
在本文中,我們學習瞭如何在不使用equals()
方法的情況下斷言兩個實例相等。
綜上所述,AssertJ 的逐字段比較提供了比較複雜對象的最簡單方法,而其他方法使用反射來比較字段,因此我們需要為複雜字段添加額外的斷言。
通過利用本文中提到的工具,即使面對沒有equals()
方法的對象,我們也可以編寫測試。
一如既往,整個源代碼都可以在 GitHub 上找到。