mockito模擬測試框架心得1


mockito是一款非常不錯的模擬測試框架,他可以按照你的需求,來生成一個模擬框架,這樣給我的工作中解決了很多問題,比如做單元測試,service依賴dao,如果我測試service層方法,但是調用dao的方法,第一他會打印很多讓我討厭的信息,但是我也不想去頻繁修改log4j,第二 這樣速度確實有些慢。
那麼,我可以模擬一個dao對象出來,並且預先設置好了,你這個dao對象調用方法,返回什麼什麼值,這樣,當我創建一個dao模擬對象,並不會真的創建dao對象,但是我通過預先設置了某些方法的返回值,讓我測試類來使用一下,這樣,達到了我需要的效果。

我把我學習、練習的代碼貼出如下:

package testmock;

import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;


public class Test01 {
	// mock模擬對象,只是模擬對象的臉,並不會真的生成該對象,所以你會發現,模擬對象在使用時,調用該對象的方法,就和正常new出來對象使用會有這麼一種感覺,那就是首位不呼應,比如給一個字段set設置了,然後緊接着get獲取,你會發現並沒有獲取到,這就是因爲壓根就沒有對象,只不過是模擬了一下該方法而已!那怎麼生成get方法可以獲取返回值,在下面的test方法中說
	@Test
	public void test01() {
		// 創建迭代器的模擬對象
		Iterator i = mock(Iterator.class);
		// 當第一次調用next方法返回hello,第二次調用返回world
		when(i.next()).thenReturn("Hello").thenReturn("World");
		// 調用next方法,拼接字符串
		String result = i.next() + " " + i.next();
		// 驗證i的times方法調用了2次
		verify(i, times(2)).next();
	}

	// 通過接口生成模擬對象,可以預先設置當用該對象的某個方法,返回的值,用作之後的判斷
	@Test
	public void test02() {
		// 創建list模擬對象
		List mock = mock(List.class);
		// 用該when().thenReturn()方式,設置了,當執行某個方法的時候,第一次、第二次……第n次返回的值
		when(mock.get(1)).thenReturn("abcd").thenReturn("eeee").thenReturn("dddd");
		// 執行方法
		Object object = mock.get(1);
		System.out.println(object);
		// 驗證是否執行了該方法
		verify(mock).get(1);
		// 當執行mock.get(3)來返回一個並不存在的元素時,就會返回該元素類型的默認值
		// 如下返回的是null,因爲引用類型默認值 null
		Object object2 = mock.get(3);
		System.out.println("mock.get(3)獲取一個並沒有預先定義傳入參數3,返回結果,所以結果爲:" + object2);
	}

	// 當然,除了可以模擬接口,也可以模擬類
	@Test
	public void test03() {
		// 創建arraylist模擬對象
		ArrayList<String> mock = mock(ArrayList.class);
		// 當使用set存、get取的時候,就會發現,並無法取出存的數據,因爲這個mock只是一個模擬對象,並不是真正的集合對象
		mock.set(1, "aa");
		// 打印結果爲null值
		System.out.println(mock.get(1));
		// 所以使用mockito提供的方式來對調用某個方法產生的返回值進行手動設置
		// 指定第一次調用get方法返回num1,第二次返回num2,這種方式官方翻譯叫存根:stubbing,個人理解爲預定義返回值的意思。
		when(mock.get(0)).thenReturn("num1").thenReturn("num2");
		// 打印獲取結果,會發現如預想的一樣
		System.out.println(mock.get(0));
		// 也可以通過驗證方法來驗證是否執行了該方法,如果確實調用了get(0)方法,則測試通過,否則測試失敗
		verify(mock).get(0);
		// 如果調用了2次mock.get(0)方法,再使用上面的方式測試會發現測試失敗
		mock.get(0);
		System.out.println("調用了2次get(0)方法");
		// 下面這句代碼是錯誤的
		// verify(mock).get(0);
		// 那麼,當調用了2次調用get(0)方法,應該使用如下格式進行測試:
		verify(mock, times(2)).get(0);

	}

	/*
	 * 參數匹配器,當給mock對象傳參,我們傳入的參數都是固定值,mockito也提供了匹配任意參數值的方法代替具體參數值來傳參。 /
	 * 如:任意一個數字可以代表數字123,任意一個字符串可以代表字符串 "abcdString" /
	 * 那麼,mockito提供的方式爲:anyInt:代表任意一個int值,anyString:代表任意一個字符串,以此類推,
	 * anyXxx代表對應的類型數據,如:8種基本數據類型,多個集合類型,Object、Class等等都有對應的參數匹配器,
	 * 可以從Matchers類中翻閱察看!
	 */
	@Test
	public void test04() {
		// 模擬map類型對象
		HashMap<String, String> mock = mock(HashMap.class);
		// 預先設定返回值
		when(mock.get(anyString())).thenReturn("第一個值").thenReturn("第二個值");
		// 察看anyString是否能匹配到調用mock.get()時候,傳入的任意參數
		// 第一次調用,傳入參數value1,anyString()能匹配
		String string = mock.get("value1");
		// 打印結果:第一個值
		System.out.println(string);
		// 第二個調用,傳入參數value2,anyString()能匹配
		string = mock.get("value2");
		// 打印結果:第二個值
		System.out.println(string);
	}

	// 精確匹配,使用上面的類型匹配器,確實很好用,但是如果說,我們需要精確匹配某一個值的話,那麼,就使用mockito提供的方法eq(),同樣,該方法的參數也可以傳遞N多種類型
	@Test
	public void test05() {
		// 創建hashMap的模擬對象
		HashMap mock = mock(HashMap.class);
		// 指定匹配value字符串,即設置map的鍵爲value對應到的值爲:指定的value鍵的值
		// 多設置幾個,看區別
		// 這裏要注意,anyXxx()方法設置返回值,一定要寫到eq()方法設置返回值前面,否則anyString會覆蓋之前設置的eq,那麼再執行的話,返回的值就都是:anyString()設置的值了。
		when(mock.get(anyString())).thenReturn("其他的值");
		when(mock.get(eq("value1"))).thenReturn("指定value鍵的值");
		when(mock.get(eq("value2"))).thenReturn("value2鍵的值");
		// 察看結果
		// 結果爲
		System.out.println(mock.get("aaa"));
		System.out.println(mock.get("value1"));
		System.out.println(mock.get("value2"));
	}

	// 如果在設置存根的時候,指定參數爲anyString(),那麼使用verify()方法進行驗證方法是否被調用,也可以使用anyString()來傳參
	@Test
	public void test06() {
		HashMap mock = mock(HashMap.class);
		when(mock.get(anyString())).thenReturn("其他的值");
		System.out.println(mock.get("aaa"));
		// 當驗證方法的時候,也可以使用anyString()與定義返回值時一致的方法來驗證該方法是否被調用。
		verify(mock).get(anyString());
	}

	// 次數匹配:驗證調用了幾次傳入同一個參數的方法,如下案例,調用add方法,a傳入1次,bb傳入2次,cc傳入3次
	@Test
	public void test07() {
		// 創建模擬對象
		ArrayList<String> mock = mock(ArrayList.class);
		mock.add("aa");
		mock.add("bb");
		mock.add("bb");
		mock.add("cc");
		mock.add("cc");
		mock.add("cc");
		// 驗證,verity方法傳參爲:mock對象,time(次數)
		// 傳入aa的add方法調用了1次
		verify(mock, times(1)).add("aa");
		// 傳入bb的add方法調用了2次
		verify(mock, times(2)).add("bb");
		// 傳入cc的add方法調用了3次
		verify(mock, times(3)).add("cc");
		// 如果不傳參 time()方法,默認是time(1)
		// verify(mock ).add("cc");//出現了3次,所以測試失敗
		verify(mock).add("aa");// 出現了1次,測試通過
		// 如果調用add從來沒有傳遞過某個參數,可以將never()傳入,進行測試,如下:
		// verify(mock, never()).add("aa"); //錯誤,因爲傳參aa是出現過的
		verify(mock, never()).add("從來沒有給add方法傳入這個字符串");
		// 也可以使用其他的
		// 最少出現一次
		verify(mock, atLeastOnce()).add("bb");
		// 出現的次數小於或等於指定的次數,如下bb出現的次數小於或等於2,傳3就測試失敗
		verify(mock, atLeast(2)).add("bb");
		// 出現的次數大於或等於指定的次數,如下cc出現的次數大於或等於5次
		verify(mock, atMost(3)).add("cc");
	}

	// 通過使用Answer接口的匿名內部類方式設置存根
	@Test
	public void test08() {
		HashMap mock = mock(HashMap.class);
		when(mock.get(anyString())).thenAnswer(new Answer<String>() {
			public String answer(InvocationOnMock invocation) throws Throwable {
				// 通過invocation可以獲取調用該方法時傳遞的參數
				Object[] arguments = invocation.getArguments();
				// 我們打印察看一下
				for (Object object : arguments) {
					System.out.println(object);

				}
				// 返回字符串,這樣當調用該方法時,返回的值就是下面這行代碼返回的內容
				return "傳入的參數爲:" + arguments[0].toString();
			}
		});
		// 獲取到的內容object爲上面定義的返回內容
		Object object = mock.get("abcd");
		System.out.println(object);
		// 驗證通過
		verify(mock).get(anyString());
	}
	//doThrow(),doAnswer(),doNothing(),doReturn() 和doCallRealMethod()的另一種編碼方式
	@Test
	public void test09() {
		List mock = mock(List.class);
		//之前我們使用doReturn doAnswer等方法格式是when(mock.get(x)).thenXxxxx(),當然也可有用另一種編碼風格書寫
		doThrow(new RuntimeException()).when(mock).get(1);
		doReturn("abcd").when(mock).get(2);
		//兩種方式,個人更喜歡前面一種方式,因爲語義更加通順。
	}
	//調用模擬對象的實際的方法
	@Test
	public void test10(){
		Person mock = mock(Person.class);
		when(mock.method()).thenCallRealMethod();
		String method = mock.method();
		System.out.println(method);
		method = mock.method();
		System.out.println(method);
	}
	//當調用方法的時候,什麼事情都不做,但是注意,該doNothing只能調用返回值void的方法,否則失敗。
	//如下案例,第一次調用該方法,什麼操作都沒有,第二次調用的話,就拋出異常。
	@Test
	public void test11(){
		Person mock = mock(Person.class);
		doNothing().doThrow(new RuntimeException()).when(mock).method2();
		mock.method2();
		System.out.println("第二次執行了");
		mock.method2();
	}
	//監視真實的對象,之前創建一個mock模擬對象,我們都是通過接口或者其本類直接生成一個模擬對象,現在直接通過實際存在的對象,創建一個mock模擬對象
	@Test
	public void test12(){
		//創建一個真實存在的對象
		ArrayList<String> arrayList = new ArrayList<String>();
		ArrayList<String> spy = spy(arrayList);
		//依然可以通過這種方式設置調用方法返回的存根
		when(spy.size()).thenReturn(100);
		System.out.println(spy.size());
		//當然,也可以像使用真實的對象那樣,使用這個監視對象spy
		spy.add("第1個參數");
		spy.add("第2個參數");
		spy.add("第3個參數");
		System.out.println(spy.get(0));
		System.out.println(spy.get(1));
		System.out.println(spy.get(2));
	}
	//當監視真實對象時,設置存根注意事項
	@Test
	public void test13(){
		ArrayList<String> arrayList = new ArrayList<String>();
		ArrayList<String> spy = spy(arrayList);
		//如下設置了存根,再調用方法的時候,就會報錯,數組角標越界
//		when(spy.get(0)).thenReturn("abcd");
//		spy.get(0);
		//使用另一種方式實現即可
		doReturn("abcd").when(spy).get(0);
		String string = spy.get(0);
		System.out.println(string);
	}
	//參數捕獲器,當我們驗證方法的時候,在驗證時候,可以使用參數捕獲器捕獲到調用方法傳入的參數
	@Test
	public void test14(){
		List mock1 = mock(List.class);
		List mock2 = mock(List.class);
		//在之前的情況,我們發現給模擬對象存入值,再取出來的話,壓根就沒存入,如下案例:
		mock1.add("abcd");
		//結果是一個空
		System.out.println(mock1.get(0));
		//那麼,怎麼樣可以捕獲到這個add添加的參數呢?這裏用到參數捕獲器
		//創建參數捕獲器
		ArgumentCaptor<List> ac = ArgumentCaptor.forClass(List.class);
		//在驗證的時候,進行捕獲,調用add方法,傳入的參數進行捕獲
		verify(mock1).add(ac.capture());
		//捕獲以後,看一下捕獲到的內容是否是我們之前設置的
		System.out.println(ac.getValue());
		//如果調用了2次add方法,那麼就如下方式捕獲
		mock2.add("AAAA");
		mock2.add("BBBB");
		verify(mock2 , times(2)).add(ac.capture());
		for (Object object : ac.getAllValues().toArray()) {
			System.out.println(object );
		}
		//如果傳入多個參數,如下案例:
		Person mock = mock(Person.class);
		mock.method3("str1", "str2");
		ArgumentCaptor<String> ac2 = ArgumentCaptor.forClass(String.class);
		verify(mock).method3(ac2.capture(),ac2.capture());
		for (Object object : ac2.getAllValues().toArray()) {
			System.out.println(object);
		}
	}
}

class Person{
	public  String method(){
		return "Person";
	}
	void method2(){
		System.out.println("method2");
	}
	void method3(String str1,String str2){}
}

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