本文是Productive Java with Ruby系列文章的第二篇,通過上一篇的介紹,我想大家對如何利用Ruby進行單元測試有了一個基本的瞭解,從這裡開始,我將和大家一起討論一些利用Ruby進行單元測試時的進階話題。
通常,新技術的引入只能降低解決問題的難度,而不是消除問題本身!
在“依賴”的原始叢林中掙紮...
通過Ruby我們可以更高效的處理資料準備的問題,但是真實的世界並不那麼簡單!隨著測試的深入,我們會越發的感覺一不小心就掙紮在“依賴”的原始叢林中!有時候似乎需要加入無數的jar包,初始化所有的組件,配置完一切的資料庫、伺服器及網路的關係,才能開始一小段簡單的測試。更痛苦的是這一切是如此的脆弱,僅僅是某人在資料庫中多加了一條資料或者更改了一部分環境配置,你苦心構建的所有測試就全部罷工了!多少次,你仰天長歎:“神啊!救救我吧...”。可神在那裡呢?
Mock
單元測試之所以有效,是因為我們遵從了快速反饋,小步快跑的原則!一次只測試一件事情!而大量依賴的解決工作明顯讓單元測試偏離的原本的目標,也讓人覺得不舒服。Mock技術就能讓我們有效擺脫在叢林中的噩夢。我們知道,在電腦的世界裡,同樣的輸入一定能得到對應的輸出,否則就是異常情況了。Mock技術本質上是通過攔截並替換指定方法的傳回值擺脫對程式實現的依賴。對於1+1這樣的輸入條件進行計算,Mock技術直接攔截原方法,替換該計算方法的傳回值為2,不關心這個演算法到底是通過網路得到的,還是通過本地計算得到的。這樣就和具體實現解藕了。
在對Java進行單元測試的時候,通常會對某個具體類或某個介面產生依賴,要解藕就需要能夠對具體類或介面進行Mock。幸好這些在JRuby中都非常的簡單,由於JtestR自動為我們引入了mocha這個Mock架構,讓我們可以更簡單的開始工作。先看一個針對HashMap的Mock測試吧:
map = mock(HashMap) #=> mock java.util.HashMap類,如果是介面可以直接new出來,例如Map.new
map.expects(:size).returns(5) #=> 類比並期望調用size方法時返回5
assert_equal 5, map.size #=>斷言,和JUnit斷言非常相似
EasyMock是個流行的開源Java Mock測試架構,在它的官方網站的文檔中剛好有如何利用Mock進行測試的樣本,為了方便說明,我將直接引用這個樣本,並用JRuby實現基於Mock的測試。首先我們有一個介面:
//共同作業者介面,用以跟蹤協作文檔的相關狀態
public interface Collaborator {
void documentAdded(String title); //當新增文檔時觸發
void documentChanged(String title); //當文檔改變時觸發
void documentRemoved(String title); //當文檔被刪除時觸發
byte voteForRemoval(String title); //當文檔被共用,並進行刪除操作是,執行投票的動作
byte[] voteForRemovals(String[] title); //同上,不過可以同時投票多個文檔
}
在這個樣本中,還有一個ClassUnderTest類實現了管理協作文檔的相關邏輯,簡化範例程式碼如下:
public class ClassUnderTest {
// ...
public void addListener(Collaborator listener) {
// 增加共同作業者
}
public void addDocument(String title, byte[] document) {
// ...
}
public boolean removeDocument(String title) {
// ...
}
public boolean removeDocuments(String[] titles) {
// ...
}
}