之前《有效使用Mock編寫java單元測試》一文中層介紹過使用EasyMock和PowerMock來編寫java單元測試,今天介紹一個更加強大的工具——JMockit。
引用單元測試中mock的使用及mock神器jmockit實踐中的java單元測試中各種Mock框架對比,就能明白JMockit有多麼強大:
JMockit是基於JavaSE5中的java.lang.instrument包開發,內部使用ASM庫來動態修改java的字節碼,使得java這種靜態語言可以想動態腳本語言一樣動態設置被Mock對象私有屬性,模擬靜態、私有方法行爲等等,對於手機開發,嵌入式開發等要求代碼儘量簡潔的情況下,或者對於被測試代碼不想做任何修改的前提下,使用JMockit可以輕鬆搞定很多測試場景。
通過如下方式在maven中添加JMockit的相關依賴:
<dependency>
<groupId>com.googlecode.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.googlecode.jmockit</groupId>
<artifactId>jmockit-coverage</artifactId>
<version>0.999.24</version>
<scope>test</scope>
</dependency>
JMockit有兩種Mock方式:基於行爲的Mock方式和基於狀態的Mock方式:
引用單元測試中mock的使用及mock神器jmockit實踐中JMockit API和工具如下:
(1).基於行爲的Mock方式:
非常類似與EasyMock和PowerMock的工作原理,基本步驟爲:
1.錄製方法預期行爲。
2.真實調用。
3.驗證錄製的行爲被調用。
通過一個簡單的例子來介紹JMockit的基本流程:
要Mock測試的方法如下:
public class MyObject {
public String hello(String name){
return "Hello " + name;
}
}
使用JMockit編寫的單元測試如下:
@Mocked //用@Mocked標註的對象,不需要賦值,jmockit自動mock
MyObject obj;
@Test
public void testHello() {
new NonStrictExpectations() {//錄製預期模擬行爲
{
obj.hello("Zhangsan");
returns("Hello Zhangsan");
//也可以使用:result = "Hello Zhangsan";
}
};
assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//調用測試方法
new Verifications() {//驗證預期Mock行爲被調用
{
obj.hello("Hello Zhangsan");
times = 1;
}
};
}
JMockit也可以分類爲非局部模擬與局部模擬,區分在於Expectations塊是否有參數,有參數的是局部模擬,反之是非局部模擬。
而Expectations塊一般由Expectations類和NonStrictExpectations類定義,類似於EasyMock和PowerMock中的Strict Mock和一般性Mock。
用Expectations類定義的,則mock對象在運行時只能按照 Expectations塊中定義的順序依次調用方法,不能多調用也不能少調用,所以可以省略掉Verifications塊;
而用NonStrictExpectations類定義的,則沒有這些限制,所以如果需要驗證,則要添加Verifications塊。
上述的例子使用了非局部模擬,下面我們使用局部模擬來改寫上面的測試,代碼如下:
@Test
public void testHello() {
final MyObject obj = new MyObject();
new NonStrictExpectations(obj) {//錄製預期模擬行爲
{
obj.hello("Zhangsan");
returns("Hello Zhangsan");
//也可以使用:result = "Hello Zhangsan";
}
};
assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//調用測試方法
new Verifications() {//驗證預期Mock行爲被調用
{
obj.hello("Hello Zhangsan");
times = 1;
}
};
}
模擬靜態方法:
@Test
public void testMockStaticMethod() {
new NonStrictExpectations(ClassMocked.class) {
{
ClassMocked.getDouble(1);//也可以使用參數匹配:ClassMocked.getDouble(anyDouble);
result = 3;
}
};
assertEquals(3, ClassMocked.getDouble(1));
new Verifications() {
{
ClassMocked.getDouble(1);
times = 1;
}
};
}
模擬私有方法:
如果ClassMocked類中的getTripleString(int)方法指定調用一個私有的multiply3(int)的方法,我們可以使用如下方式來Mock:
@Test
public void testMockPrivateMethod() throws Exception {
final ClassMocked obj = new ClassMocked();
new NonStrictExpectations(obj) {
{
this.invoke(obj, "multiply3", 1);//如果私有方法是靜態的,可以使用:this.invoke(null, "multiply3")
result = 4;
}
};
String actual = obj.getTripleString(1);
assertEquals("4", actual);
new Verifications() {
{
this.invoke(obj, "multiply3", 1);
times = 1;
}
};
}
設置Mock對象私有屬性的值:
public class ClassMocked {
private String name = "name_init";
public String getName() {
return name;
}
private static String className="Class3Mocked_init";
public static String getClassName(){
return className;
}
}
使用JMockit設置私有屬性:@Test
public void testMockPrivateProperty() throws IOException {
final ClassMocked obj = new ClassMocked();
new NonStrictExpectations(obj) {
{
this.setField(obj, "name", "name has bean change!");
}
};
assertEquals("name has bean change!", obj.getName());
}
使用JMockit設置靜態私有屬性:@Test
public void testMockPrivateStaticProperty() throws IOException {
new NonStrictExpectations(Class3Mocked.class) {
{
this.setField(ClassMocked.class, "className", "className has bean change!");
}
};
assertEquals("className has bean change!", ClassMocked.getClassName());
}
public class StateMocked {
public static int getDouble(int i){
return i*2;
}
public int getTriple(int i){
return i*3;
}
}
@Test
public void testMockNormalMethodContent() throws IOException {
StateMocked obj = new StateMocked();
new MockUp<StateMocked>() {//使用MockUp修改被測試方法內部邏輯
@Mock
public int getTriple(int i) {
return i * 30;
}
};
assertEquals(30, obj.getTriple(1));
assertEquals(60, obj.getTriple(2));
Mockit.tearDownMocks();//注意:在JMockit1.5之後已經沒有Mockit這個類,使用MockUp代替,mockUp和tearDown方法在MockUp類中
}
@Test
public void testGetTriple() {
new MockUp<StateMocked>() {
@Mock
public int getDouble(int i){
return i*20;
}
};
assertEquals(20, StateMocked.getDouble(1));
assertEquals(40, StateMocked.getDouble(2));
}
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<argLine>-javaagent:"${settings.localRepository}"/com/googlecode/jmockit/jmockit-coverage/0.999.24/jmockit-coverage-0.999.24.jar</argLine>
</configuration>
</plugin>
再次執行mvn sonar:sonar命令就可以正常統計出JMockit的單元測試覆蓋率。
更多內容請參考JMockit官方文檔:http://jmockit.googlecode.com/svn/trunk/www/tutorial.html。