java - 使用Mockito測試抽像類




unit-testing mocking abstract-class (9)

我想測試一個抽像類。 當然,我可以手動編寫一個從類繼承的模擬

我可以使用模擬框架(我正在使用Mockito)而不是手工製作模擬器嗎? 怎麼樣?


Answers

假設您的測試類與您的測試類位於相同的包中(在不同的源根目錄下),您可以簡單地創建模擬:

YourClass yourObject = mock(YourClass.class);

並像您使用其他方法一樣調用您想要測試的方法。

您需要為每種調用超級方法的具體方法期望的方法提供期望值 - 不確定您如何使用Mockito來完成這項工作,但我相信EasyMock可能會這樣做。

所有這些工作都是創建一個YouClass的具體實例,並為您節省了為每個抽象方法提供空實現的努力。

另外,我經常發現在我的測試中實現抽像類很有用,它作為我通過其公共接口進行測試的示例實現,儘管這取決於抽像類提供的功能。


您可以在測試中用匿名類擴展抽像類。 例如(使用Junit 4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

模擬框架旨在讓您更容易地模擬出您正在測試的類的依賴關係。 當你使用模擬框架來模擬一個類時,大多數框架都會動態地創建一個子類,並用代碼來替換方法實現,以檢測何時調用方法並返回一個假值。

當測試一個抽像類時,你想執行被測主體(SUT)的非抽象方法,所以一個模擬框架不是你想要的。

部分的困惑是,你所關聯問題的答案是手工製作一個從你的抽像類延伸出來的模擬。 我不會嘲笑這樣的班級。 模擬是一個用來替代依賴關係的類,可以根據期望進行編程,並且可以查詢是否滿足這些期望。

相反,我建議在測試中定義抽像類的非抽象子類。 如果這樣會導致代碼太多,那麼這可能表明您的課程難以擴展。

另一種解決方案是將測試用例本身抽像出來,用抽象方法創建SUT(換句話說,測試用例將使用Template Method設計模式)。


你可以通過使用間諜來實現這一點(儘管使用最新版本的Mockito 1.8+)。

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

真正讓我嘲笑抽像類的事實是,默認的構造函數YourAbstractClass()被調用(在模擬中缺少super()),在Mockito中也沒有任何方式默認初始化模擬屬性(例如List屬性與空的ArrayList或LinkedList)。

我的抽像類(基本上是生成的類源代碼)沒有為列表元素提供依賴關係setter注入,也沒有為它初始化列表元素(我嘗試手動添加)提供構造函數。

只有類屬性使用默認的初始化:private List dep1 = new ArrayList; 私人列表dep2 =新的ArrayList

因此,如果不使用真實的對象實現(例如,單元測試類中的內部類定義,重寫抽象方法)並且窺探真實對象(它會進行適當的字段初始化),就沒有辦法模擬抽像類。

太糟糕了,只有PowerMock會在這裡進一步提供幫助。


你可以實例化一個匿名類,注入你的mock然後測試那個類。

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

請記住,必須為抽像類ClassUnderTest的屬性myDependencyService protected可見性。


如果你只需要測試一些具體的方法而不觸及任何摘要,你可以使用CALLS_REAL_METHODS (參見Morten的答案 ),但是如果被測試的具體方法調用某些摘要或者未實現的接口方法,工作 - Mockito會抱怨“無法在java界面上調用真正的方法。”

(是的,這是一個糟糕的設計,但是一些框架,例如Tapestry 4,會對你施加壓力。)

解決方法是顛倒這種方法 - 使用普通的模擬行為(即所有的模擬/存根)和使用doCallRealMethod()明確地調用被測試的具體方法。 例如

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

更新後添加:

對於非void方法,您需要使用thenCallRealMethod() ,例如:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

否則Mockito會抱怨“檢測到未完成的殘片”。


嘗試使用自定義答案。

例如:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

它將返回抽象方法的模擬,並將調用具體方法的真實方法。


可以在這裡找到解釋: http://www.developer.com/lang/php/article.php/3604111/PHP-5-OOP-Interfaces-Abstract-Classes-and-the-Adapter-Pattern.htm : http://www.developer.com/lang/php/article.php/3604111/PHP-5-OOP-Interfaces-Abstract-Classes-and-the-Adapter-Pattern.htm

抽像類是只由程序員部分實現的類。 它可能包含一個或多個抽象方法。 抽象方法只是一個函數定義,用於告訴程序員該方法必須在子類中實現。

一個接口類似於一個抽像類; 確實接口與類和抽像類佔據相同的名稱空間。 出於這個原因,你不能定義一個與類相同的接口。 一個接口是一個完全抽象的類; 它的任何方法都沒有實現,而是從它的子類中分類,它被稱為實現該接口。

無論如何,我覺得這個接口的解釋有些令人困惑。 一個更常見的定義是: 接口定義了實現類必須履行的合同。 接口定義由公共成員的簽名組成,沒有任何實現代碼。





java unit-testing mocking abstract-class mockito