第一次得知后端开发要写单元测试的时候,心里不禁有个疑惑,这应该是测试人员的工作呀。但是既然有这要求,写就写吧。在学习单元测试的过程中,发现编写单元测试代码费时费力,就等于把完成的代码重写一遍,然后加进去假数据。并且有些方法还很难写出单元测试。通过这样的方法测试程序的逻辑性或是合法性,那我直接用postman或是apifox不是更香嘛。我就带着这样的困惑去网上找寻答案。在了解单元测试的好处时,我发现大家都在说TDD,也就是测试驱动开发。这时我才恍然大悟,原来我开始的方向就错了。TDD能规范设计原则,更好的解耦各个模块......既然内心的困惑已经解决,那就开始了解单元测试。
1.常用注解
@Mock:对对象进行mock,接管了对象的全部方法,默认不执行实际方法,返回值为基本类型的默认返回值。
@Spy:默认调用真实对象,仅对桩实现的方法进行mock。
@InjectMocks:自动实例化,将@Mock与@Spy标注的依赖注入进来。
@Captor:创建ArgumentCaptor实例,捕获其传入的参数。
注:需要通过在类上使用@RunWith(MockitoJunitRunner.class)或是在测试函数之前加入MockitoAnnotations.openMocks(this);来激活Mockito框架。
2.验证行为
import static org.mockito.Mockito.*;
List mockedList = mock(List.class);
mockedList.add("one");
//验证行为,若行为没有执行,测试失败
verify(mockList).add("one");
3.测试桩
LinkedList mockedList = mock(LinkedList.class);
//测试桩
(对于@Spy来说,thenXXX先执行对象的真实方法,然后返回thenXXX)
//执行后返回first
when(mockedList.get(0)).thenReturn("first");
//执行后抛出运行时错误
when(mockedList.get(1)).thenThrow(new RuntimeException());
//运行后返回Answer<?>
when(mockedList.get(2)).thenAnswer(new Answer());//此处自定义Answer<?>
//调用真实方法
when(mockedList.get(3)).thenCallRealMethod();
(doXXX不执行对象的真实方法,直接执行doXXX)
doReturn(“first”).when(mockedList).clear();
doThrow(new RuntimeException()).when(mockedList).clear();
doAnswer(new Answer()).when(mockedList).clear();//此处自定义Answer<?>
//调用时,什么也不做
doNothing().when(mockedList).clear();//此处调用的方法返回类型必须为void
//调用真实方法
doCallRealMethod().when(mockList).clear();
4.参数验证器
when(mockedList.get(anyInt())).thenReturn("element");
verify(mockedList).get(anyInt());
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));//不能直接放一串字符串,要使用eq()参数匹配器将字符串包起来
5.验证函数调用次数
//不输入默认为1次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
//输入则判断调用次数是否相等
verify(mockedList, times(2)).add("twice");
//其他判断条件
verify(mockedList, never()).add("never happened");
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");
verifyZeroInteractions(mockTwo, mockThree);//其中一个未交互即通过
verifyNoMoreInteractions(mockedList);//调用过其方法则失败
6.验证执行顺序
List singleMock = mock(List.class);
singleMock.add("was added first");
singleMock.add("was added second");
// 为该 mock 对象创建一个 inOrder 对象
InOrder inOrder = inOrder(singleMock);//验证多个mock对象时用”,”隔开,此处先后顺序不影响
// 确保 add 函数首先执行的是 add("was added first"),然后才是 add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
注:验证必须有序但可以跳跃
7.连续调用测试桩
// 第一次调用时返回 "one",第二次返回 "two",之后都返回 "three"
when(mock.someMethod("some arg")) .thenReturn("one", "two", "three");
注:此次也可以添加thenThrow、thenAnswer
8.重置mocks对象
reset(mock);
9.BDDMockito
import static org.mockito.BDDMockito.*;
//与测试桩类似(举一个例子)
given(mockedList.get(0)).willReturn("first");
...//此处需执行两次mockedList.get(0)验证才能通过
then(mockedList).should(times(2)).get(0);
10.序列化mock对象
List serializableMock = mock(List.class, withSettings().serializable());
//真实对象不可直接序列化
List<Object> list = new ArrayList<Object>();
List<Object> spy = mock(ArrayList.class, withSettings()
.spiedInstance(list)
.defaultAnswer(CALLS_REAL_METHODS)
.serializable());