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){}
}

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