簡體   English   中英

單元測試-檢查未覆蓋等於對象的方法調用

[英]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.

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