测试杂谈及常见单元测试框架入门

219 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

软件测试概述

  1. 测试分类

    • 软件测试分类很多种
    • 按开发阶段分的话
      分法单元测试,集成测试,功能测试,系统测试,验收测试
      常用分法单元测试,功能测试,[冒烟测试],回归测试,[验收测试]
    • 其他分类
      1. 测试技术:白盒,黑盒,灰度
      2. 测试内容:功能测试,界面测试,安全测试,兼容性测试,性能测试,压力测试,恢复测试(自我修复)
      3. 冒烟,回归
  2. 单元测试在软件生命周期中的位置

    • 最小的测试单元
  3. 展示图

image.png

先回顾单元测试的几个概念

  1. 驱动代码(Driver)

    含义调用被测试方法或者函数的代码,不同的测试框架代码结构可能不同,主要包含3部分:数据准备,调用被测方法,结果验证
  2. 桩代码(stub)

    含义代替真实代码的临时代码
  3. mock代码

    含义与桩代码类似

单元测试框架

mockito

  1. 单元测试框架:EasyMock,Mockito
  2. 大多 Java Mock 库如 EasyMock 或 Mockito 都是 expect-run-verify (期望-运行-验证)方式,而 Mockito 则使用更简单,更直观的方法。
  3. mokito好处:
    • 无需昂贵的前期启动。
    • 拥有很好的API,几乎没有时间成本。
    • 可以mock接口和类。
    • 步骤:执行前stub,在交互中验证。
    • 支持andriod

初体验

  1. 验证交互行为

    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,就会记住所有的交互,可以自行选择需要的。

  2. 方法返回

    // 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方法返回。

  3. 参数匹配

     //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.
    
  4. 验证确切的调用次数

    //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).

  5. 异常处理

       doThrow(new RuntimeException()).when(mockedList).clear();
    
       //following throws RuntimeException:
       mockedList.clear();
    
  6. 验证顺序

    
     // 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对象的顺序。

  7. 验证从未发生过调用/冗余调用

    //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);
    

基本使用方法

  1. 基本的API
    • mock()/@Mock:创建mock对象
    • spy()/@Spy: 部分mock,真实的方法会被调用,依然可以被验证和stub。
    • @InjectMocks:自动注入mock/spy字段(@Spy或者@Mock修饰的字段)
    • verify():验证指定的方法是否调用。
  2. 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()

    • 非注解(略)

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

总结:

  1. 有效提高代码质量,不适合单元测试的代码不是好代码。

  2. 提升自测效率,不需要启动spring容器。

  3. 快速排除隐藏bug,弥补功能测试不到位的地方。

  4. 推荐使用mockito,因为springboot自带。