本文已参与「新人创作礼」活动,一起开启掘金创作之路。
软件测试概述
-
测试分类
- 软件测试分类很多种
- 按开发阶段分的话
分法
单元测试,集成测试,功能测试,系统测试,验收测试常用分法
单元测试,功能测试,[冒烟测试],回归测试,[验收测试] -
其他分类
- 测试技术:白盒,黑盒,灰度
- 测试内容:功能测试,界面测试,安全测试,兼容性测试,性能测试,压力测试,恢复测试(自我修复)
- 冒烟,回归
-
单元测试在软件生命周期中的位置
- 最小的测试单元
-
展示图
先回顾单元测试的几个概念
-
驱动代码(Driver)
含义
调用被测试方法或者函数的代码,不同的测试框架代码结构可能不同,主要包含3部分:数据准备,调用被测方法,结果验证 -
桩代码(stub)
含义
代替真实代码的临时代码 -
mock代码
含义
与桩代码类似
单元测试框架
mockito
- 单元测试框架:EasyMock,Mockito
- 大多 Java Mock 库如 EasyMock 或 Mockito 都是 expect-run-verify (期望-运行-验证)方式,而 Mockito 则使用更简单,更直观的方法。
- mokito好处:
- 无需昂贵的前期启动。
- 拥有很好的API,几乎没有时间成本。
- 可以mock接口和类。
- 步骤:执行前stub,在交互中验证。
- 支持andriod
初体验
-
验证交互行为
import static org.mockito.Mockito.*; // mock creation List mockedList = mock(List.class); // using mock object - it does not throw any "unexpected interaction" exception mockedList.add("one"); mockedList.clear(); // selective, explicit, highly readable verification verify(mockedList).add("one"); verify(mockedList).clear();一旦创建mock,就会记住所有的交互,可以自行选择需要的。
-
方法返回
// you can mock concrete classes, not only interfaces LinkedList mockedList = mock(LinkedList.class); // stubbing appears before the actual execution when(mockedList.get(0)).thenReturn("first"); // the following prints "first" System.out.println(mockedList.get(0)); // the following prints "null" because get(999) was not stubbed System.out.println(mockedList.get(999));mock方法返回。
-
参数匹配
//stubbing using built-in anyInt() argument matcher when(mockedList.get(anyInt())).thenReturn("element"); //stubbing using custom matcher (let's say isValid() returns your own matcher implementation): when(mockedList.contains(argThat(isValid()))).thenReturn(true); //following prints "element" System.out.println(mockedList.get(999)); //you can also verify using an argument matcher verify(mockedList).get(anyInt()); //argument matchers can also be written as Java 8 Lambdas verify(mockedList).add(argThat(someString -> someString.length() > 5));注意:参数如果过使用匹配器,所有参数必须使用匹配器提供。
verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //above is correct - eq() is also an argument matcher verify(mock).someMethod(anyInt(), anyString(), "third argument"); //above is incorrect - exception will be thrown because third argument is given without an argument matcher. -
验证确切的调用次数
//using mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //following two verifications work exactly the same - times(1) is used by default verify(mockedList).add("once"); verify(mockedList, times(1)).add("once"); //exact number of invocations verification verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); //verification using never(). never() is an alias to times(0) verify(mockedList, never()).add("never happened"); //verification using atLeast()/atMost() verify(mockedList, atMostOnce()).add("once"); verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("three times"); verify(mockedList, atMost(5)).add("three times");默认是times(1), 所以可以显示省略times(1).
-
异常处理
doThrow(new RuntimeException()).when(mockedList).clear(); //following throws RuntimeException: mockedList.clear(); -
验证顺序
// A. Single mock whose methods must be invoked in a particular order List singleMock = mock(List.class); //using a single mock singleMock.add("was added first"); singleMock.add("was added second"); //create an inOrder verifier for a single mock InOrder inOrder = inOrder(singleMock); //following will make sure that add is first called with "was added first", then with "was added second" inOrder.verify(singleMock).add("was added first"); inOrder.verify(singleMock).add("was added second"); // B. Multiple mocks that must be used in a particular order List firstMock = mock(List.class); List secondMock = mock(List.class); //using mocks firstMock.add("was called first"); secondMock.add("was called second"); //create inOrder object passing any mocks that need to be verified in order InOrder inOrder = inOrder(firstMock, secondMock); //following will make sure that firstMock was called before secondMock inOrder.verify(firstMock).add("was called first"); inOrder.verify(secondMock).add("was called second"); // Oh, and A + B can be mixed together at will创建InOrder对象实现,支持多个mock对象的顺序。
-
验证从未发生过调用/冗余调用
//using mocks - only mockOne is interacted mockOne.add("one"); //ordinary verification verify(mockOne).add("one"); //verify that method was never called on a mock verify(mockOne, never()).add("two"); //verify that other mocks were not interacted verifyZeroInteractions(mockTwo, mockThree);//using mocks mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); //following verification will fail verifyNoMoreInteractions(mockedList);
基本使用方法
- 基本的API
- mock()/@Mock:创建mock对象
- spy()/@Spy: 部分mock,真实的方法会被调用,依然可以被验证和stub。
- @InjectMocks:自动注入mock/spy字段(@Spy或者@Mock修饰的字段)
- verify():验证指定的方法是否调用。
- mock方式:
- 注解3种方式
-
@RunWith(MockitoJUnitRunner.class) 基于junit4或者
-
before方法里添加 MockitoAnnotations.openMocks(this);
//目标service @InjectMocks private CommonService commonService; //mock的service @Mock private CustomerService customerService; @Test public void checkLoginNameAndGetCustomer() { //mock方法设置返回值 Mockito.when(this.customerService.findByLoginName("cnn")) .thenAnswer((InvocationOnMock mock) -> new CustCustomer()); //测试目标方便并验证 Assert.notNull(this.commonService.checkLoginNameAndGetCustomer("cnn"), "为空"); //验证mock的方法是否有调用 Mockito.verify(this.customerService).findByLoginName("cnn"); } -
MockitoJUnit.rule()
略
-
- 非注解(略)
- 注解3种方式
easyMock
初体验
@RunWith(EasyMockRunner.class)
public class ExampleTest {
//目标类
@TestSubject
private ClassUnderTest classUnderTest = new ClassUnderTest(); // 2
@Mock
private Collaborator mock; // 1
@Test
public void testRemoveNonExistingDocument() {
// ... 期望操作
replay(mock);
classUnderTest.removeDocument("Does not exist");
// ... 验证
verify()
}
}
基本使用方法
常见注解
@Mock,@TestSubject等
mockito与EasyMock的区别
https://github.com/mockito/mockito/wiki/Mockito-vs-EasyMock
总结:
-
有效提高代码质量,不适合单元测试的代码不是好代码。
-
提升自测效率,不需要启动spring容器。
-
快速排除隐藏bug,弥补功能测试不到位的地方。
-
推荐使用mockito,因为springboot自带。