深度測試常陷入「依賴叢林」:需啟動資料庫、服務器、配置網絡,脆弱且緩慢。Mock 技術攔截並替換指定方法返回值,切斷對實際實現的依賴,實現真正的單元測試——快速、隔離、穩定。
JtestR 內建 Mocha 框架,語法極具 DSL 風格:
map = mock(HashMap) # Mock 類
map.expects(:size).returns(5) # 期望 size() 返回 5
assert_equal 5, map.size
public interface Collaborator {
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}
public class ClassUnderTest {
public void addListener(Collaborator listener) { ... }
public void addDocument(String title, byte[] doc) { ... }
public boolean removeDocument(String title) { ... }
public boolean removeDocuments(String[] titles) { ... }
}
import "org.easymock.samples.ClassUnderTest"
import "org.easymock.samples.Collaborator"
unit_tests do
cut = ClassUnderTest.new
mock = Collaborator.new # Mock 介面直接 new
cut.addListener(mock)
test "001 remove none existing document" do
cut.removeDocument("Does not exist")
end
test "002 add document" do
mock.expects(:documentAdded).with("New Document")
cut.addDocument("New Document", [].to_java(:byte)) # Ruby 陣列轉 Java byte[]
end
test "003 add and change document" do
mock.expects(:documentAdded).with("Document")
mock.expects(:documentChanged).with("Document").times(3) # DSL: times(3)
4.times { cut.addDocument("Document", [].to_java(:byte)) }
end
test "004 vote for removal" do
mock.expects(:voteForRemoval).with("Document").returns(42)
mock.expects(:documentRemoved).with("Document")
assert_equal true, cut.removeDocument("Document")
end
end
| 特性 | JRuby + Mocha | Java + EasyMock |
|---|---|---|
| 語法風格 | DSL,語義化極強(.with().times().returns()) | API 呼鏈,較冗長 |
| 執行順序 | 按方法名字母序,單上下文 | JUnit 多線程,順序不保證 |
| 類型轉換 | 需顯式 .to_java(:byte) | 原生類型安全 |
啟動 JtestR 內建測試服務器:mvn jtestr:server,後續 mvn test 將複用 JVM,大幅縮短啟動時間。
JRuby + Mocha 以 DSL 風格 Mock API、極簡語法、強大語義表達力,大幅降低測試依賴處理難度。但 Mock 屬白盒測試,內部實現變更仍可能導致測試失效——技術降低難度,不消除問題本質。