单元测试Junit5+Mockito3+Assertj

发布时间:2022-07-01 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了单元测试Junit5+Mockito3+Assertj脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

单元测试框架介绍与实践

为什么单元测试

  • 天然的方法说明文档
  • 代码质量的保证
  • 持续重构的定心丸

什么是好的单元测试

  • 单元测试需要自动化执行(CI)
  • 单元测试需要快速执行
  • 单元测试不应该依赖测试的执行顺序,UT相互之间是独立的
  • 单元测试不应该依赖数据库,文件IO或任何长耗时任务。相反,单元测试需要与外部依赖隔离。
  • 单元测试是持续稳定的,在任何时候,任何环境中都是可执行的
  • 单元测试要有意义
  • 单元测试要尽量简短,及时维护

单元测试原则

  • UT要可靠,如果UT失败,那么生产环境也可能在某种情况下失败
  • UT要提供可持续的验证
    • 函数可持续验证
    • 问题能够立即发现
    • 在手动回归测试之前,就可以快速测试,节省时间
  • UT要快速执行,快速反馈
  • 无障碍的启动和运行

测试什么

  • 某个模块(如Service层的方法),公共的API,不要对PRivate方法单元测试
  • 常用的输入输出组合
  • 边界条件和异常

JunIT

常用注解

@H_360_66@
注解 说明
@BeforeClass 在所有测试方法执行前执行一次,一般在其中写上整体初始化的代码,要求方法static
@AfterClass 在所有测试方法后执行一次,一般在其中写上销毁和释放资的代码,要求方法static
@Before 在每个@test注解的方法执行之前执行
@After 在每个@Test注解的方法执行之后执行
@Test 测试方法,一般的测试用例
@Test(timeout = 1000) 测试方法执行超过1000毫秒后算超时,测试将失败
@Test(expected = Exception.class) 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
@Ignore("not ready yet") 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
@Mock与@InjectMock 将被@Mock注解标注的对象注入到被@InjectMock的对象里

详细注解:https://junit.org/junit5/docs/current/user-guide/

Mockito

  • 一版流程:given-when-then
@Test
public void thenReturn() {    
   //mock一个List类    
    List<String> list = mock(List.class);    
    //预设当list调用get()时返回hello,world    
    Mockito.when(list.get(anyInt())).thenReturn("hello,world");    
    String result = list.get(0);    
    Assert.assertEquals("hello,world", result);
}
//模拟创建一个List对象
List<Integer> mock =  Mockito.mock(List.class);
//调用Mock对象的方法
mock.add(1);
mock.clear();
//验证方法是否执行
Mockito.verify(mock).add(1);
Mockito.verify(mock).clear();
  • 多次触发返回不同值
//mock一个Iterator类
List<String> list = mock(List.class);
//预设当list调用get()时第一次返回hello,第n次都返回world
Mockito.when(list.get(anyInt())).thenReturn("hello").thenReturn("world");
// 下面这句和上面的语句等价
Mockito.when(list.get(anyInt())).thenReturn("hello","world");


//使用mock的对象
String result = list.get(0) + " " + list.get(1) + " " + list.get(2);
//验证结果
Assert.assertEquals("hello world world",result);
  • 模拟抛出异常
@Test(exPEcted = IOException.class)//期望报IO异常
public void when_thenThrow() throws IOException{    
    OutputStream mock = Mockito.mock(OutputStream.class);    
    //预设当流关闭时抛出异常    
    Mockito.doThrow(new IOException()).when(mock).close();    
    mock.close();
}
  • 匹配任意参数

Mockito.anyInt() 任何 int 值 ; Mockito.anyLong() 任何 long 值 ; Mockito.anyString() 任何 String 值 ;

Mockito.eq(Object) 任何匹配值 ;

Mockito.any(XXX.class) 任何 XXX 类型的值 ;

Mockito.any() 任何对象 等

List list = Mockito.mock(List.class);
//匹配任意参数
Mockito.when(list.get(Mockito.anyInt())).thenReturn(1);
Assert.assertEquals(1,list.get(1));Assert.assertEquals(1,list.get(999));

注意:使用了参数匹配,那么所有的参数都必须通过matchers来匹配

@Testpublic void matchers() {    
    Comparator comparator = mock(Comparator.class);   
    comparator.COMpare("nihao", "hello");    
    //如果你使用了参数匹配,那么所有的参数都必须通过matchers来匹配    
    Mockito.verify(comparator).compare(anyString(), Mockito.eq("hello"));    
    //下面的为无效的参数匹配使用    
    // Mockito.verify(comparator).compare(anyString(),"hello");}
  • void方法
@Test
public void Test() {    
	A a = Mockito.mock(A.class);    
    //void 方法才能调用doNothing()    
    Mockito.doNothing().when(a).setName("bb");   
    a.setName("bb");   
    // 断言失败    
    Assert.assertEquals("bb",a.getName());
}
class A {    
    private String name;    
    public void setName(String name){
    this.name = name;    
    }    
    public String getName(){        
        return name;    
    }
}
  • 预期回调接口生成期望值
@Test
public void answerTest(){
      List mockList = Mockito.mock(List.class);
      //使用方法预期回调接口生成期望值(Answer结构)
      Mockito.when(mockList.get(Mockito.anyInt())).thenAnswer(new CustomAnswer());
      Assert.assertEquals("hello world:0",mockList.get(0));
      Assert.assertEquals("hello world:999",mockList.get(999));
  }
  private class CustomAnswer implements Answer<String> {
      @override
      public String answer(InvocationOnMock invocation) throws Throwable {
          Object[] args = invocation.getarguments();
          return "hello world:"+args[0];
      }
  }
等价于:(也可使用匿名内部类实现)
@Test
 public void answer_with_callback(){
      //使用Answer来生成我们我们期望的返回
      Mockito.when(mockList.get(Mockito.anyInt())).thenAnswer(new Answer<Object>() {
          @Override
          public Object answer(InvocationOnMock invocation) throws Throwable {
              Object[] args = invocation.getArguments();
              return "hello world:"+args[0];
          }
      });
      Assert.assertEquals("hello world:0",mockList.get(0));
     Assert. assertEquals("hello world:999",mockList.get(999));
  }

其他

注意bytebuddy的版本,mockito3.3.3 依赖 bytebuddy1.10.5

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.10.5</version>
</dependency>

maven执行测试用例的插件,保证流水线执行测试:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>;maven-surefire-plugin</artifactId>
    <version>3.0.0-M3</version>
    <configuration>
        <excludes>
            <exclude>some test to exclude here</exclude>
        </excludes>
    </configuration>
</plugin>

个人理解

  • UT代码要简单
  • 善用Mock
  • UT的命名和注释很重要
  • UT并不能保证完全没bug
  • 如果发现单元测试不好写,肯定是代码有问题,首先选择重构代码,实在不行再考虑使用框架的高级特性

参考

https://www.jianshu.com/p/ecbd7b5a2021 本文主要参考资料

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.htML Mockito官方文档

https://time.geekbang.org/column/article/185684 极客时间-对于单元测试和重构的理解

脚本宝典总结

以上是脚本宝典为你收集整理的单元测试Junit5+Mockito3+Assertj全部内容,希望文章能够帮你解决单元测试Junit5+Mockito3+Assertj所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。