[英]Why doesn't this equals method properly override the object.equals method?
[英]Unit Test - Check a method call with an object that doesn't override equals
這是一個關於如何使用模擬對象對Java類進行單元測試的一般問題。
我可以用這個例子來總結我的問題。
假設我有一個名為MyInterface.java的接口和一個不覆蓋equals()的“ TwoString”對象
“ TwoString.java”
private String string1;
private String string2;
public TwoString(String string1, String string2) {
this.string1 = string1;
this.string2 = string2;
}
...getters..setters..
“ MyInterface.java”
void callMe(TwoString twoString);
然后,我有“ MyClass.java”對象。 它的構造函數接受MyInterface的具體實現。
MyClass methodToTest()包含以某種方式創建TwoString對象的邏輯。 假設它將被創建為
new TwoString("a","b")
因此,在調用methodToTest()時,它將創建此TwoString對象,該對象將傳遞給接口方法callMe(TwoString twoString) 。
我基本上想模擬接口。 使用此模擬創建MyClass對象。 然后,驗證是否使用TwoString的特定實例調用了模擬方法。
我正在使用EasyMock,這是一些Java代碼
“ MyClassTest.java”
public void test() throws Exception {
MyInterface myInterfaceMock = createMock(MyInterface.class);
MyClass myClass = new MyClass(myInterfaceMock);
myInterfaceMock.callMe(new TwoString("a","b")); <--- fails here
expectLastCall();
replay(myInterfaceMock);
myClass.methodToTest();
verify(myInterfaceMock);
問題來了。 我在通話中期望的TwoString對象
myInterfaceMock.callMe(new TwoString("a","b"));
與MyClass.methodToTest()中生成的代碼不同,因為TwoString.java不會覆蓋equals。
我可以使用TwoString實例跳過該問題
myInterfaceMock.callMe((TwoString)anyObject());
但是我想確定接口方法是使用TwoString的特定實例調用的,該實例包含“ a”作為string1和“ b”作為string2。
在這種情況下,TwoString對象非常簡單,將很容易覆蓋equals方法-但是如果我需要檢查更復雜的對象該怎么辦。
謝謝
我將通過此示例使它更具可讀性
public class MyClassTest {
private MyClass myClass;
private TaskExecutor taskExecutorMock;
@Before
public void setUp() throws Exception {
taskExecutorMock = createMock(TaskExecutor.class);
myClass = new MyClass(taskExecutorMock);
}
@Test
public void testRun() throws Exception {
List<MyObj> myObjList = new ArrayList<MyObj>();
myObjList.add(new MyObj("abc", referenceToSomethingElse));
taskExecutorMock.execute(new SomeTask(referenceToSomethingElse, ???new SomeObj("abc", referenceToSomethingElse, "whatever"))); <--- ??? = object created using data in myObjList
expectLastCall();
replay(taskExecutorMock);
myClass.run(myObjList);
verify(taskExecutorMock);
}
}
SomeObj =由myClass.run()使用myObjList中包含的數據創建的對象。
假設SomeObj來自第三方庫,它不會覆蓋equals。
我想確保使用該SomeObj的特定實例調用taskExecutorMock.execute()方法
如何測試myClass.run()實際是否使用正確的實例調用taskExecutorMock方法
可以通過org.easymock.IArgumentMatcher
使用自定義等於匹配方法。
它看起來應該像這樣:
private static <T extends TwoString> T eqTwoString(final TwoString twoString) {
reportMatcher(new IArgumentMatcher() {
/** Required to get nice output */
public void appendTo(StringBuffer buffer) {
buffer.append("eqTwoString(" + twoString.getString1() + "," + twoString.getString2() + ")");
}
/** Implement equals basically */
public boolean matches(Object object) {
if (object instanceof TwoString) {
TwoString other = (TwoString) object;
// Consider adding null checks below
return twoString.getString1().equals(object.getString1()) && twoString.getString2().equals(object.getString2());
}
else {
return false;
}
}
});
return null;
}
並使用如下:
myInterfaceMock.callMe(eqTwoString(new TwoString("a","b")));
一些細節可能不正確,但從本質上講,這是我之前所做的。 EasyMock文檔中還有另一個示例和更詳盡的解釋。 只需搜索IArgumentMatcher
。
首先-您可能是說“覆蓋等號”而不是實現,因為所有類都有一些等號實現(如果它們不覆蓋其他任何東西,它們將從Object繼承)。
在這種情況下,答案很簡單-所有值對象實際上都應該實現equals和hashcode。 無論是像TwoString這樣的簡單對象,還是您提到的更復雜的對象,檢查它是否與其他對象相等都應該由該對象負責。
唯一的其他選擇是基本上在測試代碼中解構對象-因此,代替
assertEquals(expected, twoStr);
你會做
assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());
希望您可以從至少三個方面看到這是不好的。 首先,您基本上是在復制該類自己的equals()方法中的邏輯; 在任何要比較這些對象的地方,都必須編寫相同的代碼。
其次,您只能看到對象的公共狀態,很可能有一些私有狀態導致兩個看似相似的對象不相等(例如,Lift類可以具有公共可訪問的“ floor”屬性,而私有“向上或向下” ”)。
最后,對於第三方類而言,基本上是在與TwoString的內部弄混以試圖確定事物是否相等,這違反了Demeter的定律。
對象本身應實現自己的equals()方法-純凈且簡單。
看看Jakarta Commons Lang: EqualsBuilder.reflectionEquals()
盡管我同意dtsazza的觀點 ,即所有值對象都應具有equals()
(和hashCode()
)方法,但它們並不總是適合於測試:大多數值對象將基於鍵而不是基於所有字段。
同時,我對要檢查所有字段的任何測試都持反對態度:在我看來,這是“確保此方法不會更改我不打算更改的內容”。 這是一個有效的測試,在某種程度上說是一個非常好的測試,但是您感到有一點恐懼的感覺。
在這種情況下,TwoString對象非常簡單,將很容易覆蓋equals方法-但是如果我需要檢查更復雜的對象該怎么辦。
一旦您的對象變得如此復雜,以至於您無法從其他地方簡單地檢查它們是否相等,就應該重構並將其作為依賴項注入。 這將改變設計,但通常情況會更好。
您似乎還依賴於有關類的內部行為的一些知識。 上面是兩個類之間的交互測試,它們仍然可以工作,但是您所測試的組件集越大,您實際上談論“單元”測試的內容就越少。 在某個時候,您離開了單元測試的領域,開始進行集成測試,在這種情況下,最好使用全面的測試工具,並在某些位置隔離行為,從而更好。
您可以在Mockito 1.8中使用參數捕獲器來實現。
http://mockito.googlecode.com/svn/branches/1.8.0/javadoc/org/mockito/Mockito.html#15
我知道您正在使用EasyMock,但更改為Mockito很容易,而且效果更好!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.