Jmock

 

物種起源

任何框架和lib都有其實際需求來源,純粹爲了技術或概念而code的項目,大概都還在象牙塔裏。

 

Jmock 的首頁上第一句話相當簡明:

JMock is a library that supports test-driven development of Java code with mock objects.

 

瞭解JMOCK,從兩個概念入手:


TDD
Unit tests are so named because they each test one unit of code. Whether a module of code has hundreds of unit tests or only five is irrelevant. A test suite for use in TDD should never cross process boundaries in a program, let alone network connections. Doing so introduces delays that make tests run slowly and discourage developers from running the whole suite. Introducing dependencies on external modules or data also turns unit tests into integration tests. If one module misbehaves in a chain of interrelated modules, it is not so immediately clear where to look for the cause of the failure.

 

爲什麼叫unit test(單元測試)?單元測試就是測試單個method的功能,這也就是爲什麼我們寫junit測試時,總是一個個testMethodXXX。TDD裏提倡單元測試的單元性,需要將外部依賴性(比如DB, network)隔離出來。有兩個好處,一是讓test 飛一飛,跑的更快,二是界定boundaries,因爲邏輯交叉,一個邏輯失敗,會導致好多測試失敗。

 

老實說,這兩點有些似是而非。test越接近產品環境,越有可能發現產品環境下的bug。另外continuous integration中說測試可以放在晚上跑,所以速度慢不是問題。連鎖反應是個問題,可能會導致找bug時道路曲折。但測試失敗應該不是個經常發生的問題把。

 

anyway,怎麼保證隔離呢?that's why need mock..

 

mock object

In a unit test, mock objects can simulate the behavior of complex, real (non-mock) objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test

 

mock object就是使用method stub來實現定義的interface,比如:

BEGIN ThermometerRead(Source insideOrOutside)
        RETURN 28
END ThermometerRead

 

方法不作任何邏輯處理,直接return一個預定的結果。

The Design principle 

 

針對上述概念,最簡單直接的一種實現方法是每個接口增加一個test實現。在需要的地方使用test實現來作測試。這種方法理論上可行, why?

首先你會形成class disaster,增加無數個test class。其次缺少靈活性,在stub實現往往是輸出一個固定結果,而測試時有可能需要針對不同條件來返回不同值。

 

關於class disaster的問題, jmock使用代理來避免。關於return的問題,jmock引入了action的概念。

簡單點,實現原則:

  • proxy 
  • 聲明方法的調用
  • 聲明方法的輸入/輸出

其具體實現,本人到沒有dig。只是對其中的一種語法比較感興趣, Expectations使用雙大括號聲明{{ }} (Double-Brace Block)。java中有這種語法?本文結尾給出揭曉

 

The usage

hello, jmock

首先引入jmock的lib,使用eclipse中的maven-plugin自動添加

  • jmock-2.5.1.jar
  • hamcrest-core-1.1.jar
  • hamcrest-library-1.1.jar

可能會有類加載的問題,因爲junit 裏面也打包了hamcrest。如果有java.lang.SecurityException class "org.hamcrest.TypeSafeMatcher" 問題。可以將junit改爲junit-no-dep,或者改變類加載的搜索順尋。

 

要測試的方法爲Publisher的publish方法

 

 

 

我們可以使用一個實現好的Subscriber,在publish之後,通過Subscriber的行爲來判斷msg是否receive。顯然,如引言中所述,引入Subscriber的依賴,單元測試的邊界變得模糊,我們到底是測試publish呢,還是測試receive呢?

 

jmock 測試code如下:

 

 

1. 生成要測試的object,Publisher爲真publisher, Subscriber則是mock生成。

2. 讓target對象使用mock對象,或者將mock對象設置到測試的target對象中

3. 聲明mock對象的expectations,就是mock的哪些方法被調用,調用的輸入輸出是什麼。例子中,聲明subscriber的receive被調用一次,輸入參數是message,沒有輸出。

4. 運行要測試的方法

5. 進行verify, context.assertIsSatisfied(),來assert調用是否和expectation一致。在實際測試中,我們往往加上自己的assert,assert測試方法的return(本例中target方法也沒有輸出)

 

從上面可以看出,使用jmock的一個關鍵部分就是聲明expectations。

Expectations

expectations的語法如下:
invocation-count (mock-object).method(argument-constraints);
    inSequence(sequence-name);
    when(state-machine.is(state-name));
    will(action);
    then(state-machine.is(new-state-name));
看起來似乎很多不?除了 invocation count 和mock object外,其他都是optional.  其核心無非就是declare方法的調用,其他都浮雲。
(mock-object).method(argument-constraints)  will(action);
基本對應以下java語法
Object xx = mock.method(parameter); 
return xxx;

 

 

具體每個部分說明如下:

Invocation count

方法觸發的次數,使用較多的是

oneOf 觸發一次

exactly(n).of 觸發n次

allowing 任意次

Argument Matchers

方法參數的匹配,使用with聲明,主要使用same, equal, any等

 

oneOf (mock).doSomething(with(equal(1))); //參數爲單一參數,值equal1

oneOf (mock).doSomething(with(same(1)))  // 參數爲單一參數,直 == 1

oneOf (mock).doSomething(with(any(Class<T> type))) //參數爲單一參數,只要是class類型即可。

 

Actions

method invocation後的,使用will聲明,主要使用return和throwException等

 

will(returnValue(v))

will(throwException(e))

States

狀態機都出來了。哥還真用了一把。在最先的版本中,我們使用Spring來注入Mockery,所有test共用了一個Mockery,這樣各個unit test的Expecting相互影響。怎麼隔離開來呢?加上state condition

 

  • 在每個測試的開頭聲明一個States

final States pen = context.states("mymethod").startsAs("start");

  • 在expecting的method invocation加入when

 

 

oneOf (turtle).forward(10); when(pen.is("start"));
  • 在測試結束時,將狀態機的狀態設置爲非start的其他狀態

 

Sequences

invocation 的順序,語法類似於States

Invocations that are expected in a sequence must occur in the order in which they appear in the test code. A test can create more than one sequence and an expectation can be part of more than once sequence at a time.

Mocking class instead of interface

Add jmock-legacy-2.5.1.jar, cglib-nodep-2.1_3.jar and objenesis-1.0.jar to your CLASSPATH. 連final class夜可以被mock,引入jdave即可

 

 

Annotation

自動生成mock對象

 

自動生成States/Sequence
@Auto States pen;
@Auto Sequence events;

 

The limits

  • closely couple: unit test 和方法的實現緊耦合。這樣如果你的方法或者類refactor,unit test要做相應的修改
  • 當mock object數量很多,維護的成本相應增加
  • mock的行爲如果不正確,有可能導致unit test變成non-sense,檢測不出bug

 

由於我們系統的domain model很複雜(遺留原因),這時引入jmock,我們發現生成mock對象的過程,似乎是將所有Domain object和implementation又重新寫了一遍。所有上述limit一下子暴露無遺。

 

目前暫時沒有想到有效的應對下述系統的測試方法:

1.  db design is complex/legacy

2.  domain model (wsdl/xsd) design is complex. because we reuse the old business-rules, while those business-rules is not examine and verify carefully in the past.

 

一個遺留的db加上一個複雜的domain model,雖然business logic很簡單,沒有任何複雜的商業計算,只是簡單的DAO -> WSDL Bean的操作,實現起來讓開發人員覺得紛繁蕪雜,難以入手。

 

在本文完成之時,我們決定仍然使用Jmock, 效果如何,下回分解。

關於DoubleBrace

 

 

這是一個trick,DoubleBraceInitialization

 

The first brace creates a new AnonymousInnerClass, the second declares an instance initializer block that is run when the anonymous inner class is instantiated

 

第一個大括號聲明瞭一個Annoymous的InnerClass,例子中聲明瞭一個Hashset的子類;第二個括號則是 instance initializer block,實例的初始化模塊。expectations的聲明相當明瞭。

 

修訂

使用mock,除了前文所說的isolate(隔離)外,模擬(simulate)也是另外一個重要原因,比如瀏覽器表單提交。。。

更多

參見cookbook

 

http://www.jmock.org/cookbook.html

 

關於更多的java Idioms

http://www.c2.com/cgi/wiki?JavaIdioms

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章