Java单元测试框架与实践

本文首先在理论上归纳了单元测试在宏观和微观层面要遵循的基本原则,以及测试覆盖率的要求和评价维度。然后具体阐述了笔者实战中总结的基于Junit + Mockito 的单元测试框架和具体实施方法,并给出了相应的demo代码。

本文主要参考和引用了《码出高效:Java开发手册》,Junit5、mockito等官方文档以及若干篇相关博客的内容,具体可见文末参考链接部分。

基本原则

宏观层面:AIR原则

  • A:Automatic(自动化)
    全自动执行,输出结果无需人工检查,而是通过断言验证。
  • I:Independent(独立性)
    分层测试,各层之间不相互依赖。
  • R:Repeatable(可重复)
    可重复执行,不受外部环境( 网络、服务、中间件等)影响。

微观层面:BCDE原则

  • B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
  • C: Correct,正确的输入,并得到预期的结果。
  • D: Design,与设计文档相结合,来编写单元测试。
  • E : Error,单元测试的目标是证明程序有错,而不是程序无错。为了发现代码中潜在的错误, 我们需要在编写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果。

Mock

由于单元测试只是系统集成测试前的小模块测试,有些因素往往是不具备的,因此需要进行Mock。例如:

  • 功能因素。比如被测试方法内部调用的功能不可用。
  • 时间因素。比如双十一还没有到来,与此时间相关的功能点。
  • 环境因素。政策环境,如支付宝政策类新功能,多端环境, 如PC 、手机等。
  • 数据因素。线下数据样本过小,难以覆盖各种线上真实场景。

覆盖率要求

  • 粗粒度覆盖:类覆盖率和方法覆盖率应达到100%。
  • 细粒度覆盖:行覆盖、分支覆盖、条件判定覆盖、路径覆盖等,相应的覆盖率应考虑上述原则与因素。

测试框架

底层测试框架:junit

Junit主流还是junit4,最新版本是4.12(2014年12月5日),现在最新的是junit5(JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage)。junit5正式版本的发布日期是2017年9月11日,目前最新的版本是5.5.2(2019年9月9日)。我们项目底层选择了junit5。

Mock工具

  • 方法:mockito
    目前,在 Java 阵营中主要的 Mock 测试工具有 Mockito、JMock、EasyMock 等。我们选择了功能更强大且容易上手的Mockito。
    另外,Mockito不支持static的的方法的mock,要使用PowerMock来模拟。但是PowerMock似乎现在还不支持junit5,我们没有使用。
  • 模拟数据生成:jmockdata
  • Redis:redis-mock
  • 数据库:h2
  • 接口:MockMVC

测试实施方法

该部分以Spring Boot项目为例,介绍单元测试中主要碰到的需求与问题以及相应的实施方法。开发环境选择IntelliJ IDEA 2018。

测试需求分析

原则 DAO层 service层 controller层
Automatic 底层测试框架
Independent/Repeatable Mock DB Mock DAO层接口、第三方API Mock service层接口、Restful请求
Border/Error 参数化测试;重复测试;条件测试;分类测试;模拟数据生成
Correct 断言
Design 测试报告自动生成

Junit5的基本使用

Maven导入依赖
<dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-commons</artifactId><version>1.5.0</version><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><version>5.5.2</version><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.5.2</version><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-params</artifactId><version>5.5.2</version><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.5.2</version><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.5.2</version><scope>test</scope>
</dependency>
常用注解

最基本的测试代码
import com.demo.entity.User;
import com.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
public class UserTest {@Autowiredprivate UserService userService;@Testvoid userSelectByName(String userName) {User user = userService.selectByName(userName);assertNotNull(user);}}
参数化测试

使用@ParameterizedTest注解,参数化测试使得测试可以测试多次使用不同的参数值。
参数源:
@ValueSource是最简单的来源之一。它允许你指定单个数组的文字值,并且只能用于为每个参数化的测试调用提供单个参数。
@NullSource注解用来给参数测试提供一个null元素,要求传参的类型不能是基本类型(基本类型不能是null值)
@EmptySource为java.lang.String, java.util.List, java.util.Set, java.util.Map, primitive arrays等类型提供了空参数。
@NullAndEmptySource 注解为上边两个注解的合并。
@EnumSource能够很方便地提供Enum常量。该注解提供了一个可选的names参数,你可以用它来指定使用哪些常量。如果省略了,就意味着所有的常量将被使用。

@SpringBootTest
public class UserTest {@Autowiredprivate UserService userService;@ParameterizedTest@NullAndEmptySource@ValueSource(strings = {"张三", "Thomas", "000001"})void userSelectByName(String userName) {User user = userService.selectByName(userName);assertNotNull(user);}}
重复测试

@RepeatedTest中填入次数可以重复测试。

@SpringBootTest
public class UserTest {@Autowiredprivate UserService userService;@RepeatedTest(3)void userSelectByName(String userName) {User user = userService.selectByName(userName);assertNotNull(user);}}
条件测试
//禁用测试
@Disabled("Disabled until bug #99 has been fixed")
void userSelectByName(String userName) {}//操作系统条件
@EnabledOnOs(MAC)
@DisabledOnOs(WINDOWS)
void userSelectByName(String userName) {}//脚本条件
@EnabledIf("2 * 3 == 6")
@DisabledIf("Math.random() < 0.314159")
void userSelectByName(String userName) {}// 基于标签过滤
@Tag("fast")
@Tag("model")
void userSelectByName(String userName) {}
测试执行顺序

可以在类上标注@TestMethodOrder来声明测试方法要有执行顺序,里边可以传入三种类Alphanumeric、OrderAnnotation、Random,分别代表字母排序、数字排序、随机。然后对方法加@Order注解里边传入参数决定顺序。

import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;@TestMethodOrder(OrderAnnotation.class)
@SpringBootTest
public class UserTest {@Autowiredprivate UserService userService;@Test@Order(1)void userSelectByName(String userName) {User user = userService.selectByName(userName);assertNotNull(user);}@Test@Order(2)void method2() {}@Test@Order(3)void method3() {}}
断言

所有的JUnit Jupiter断言都是 org.junit.jupiter.api.Assertions类中static方法。可以使用Lambda表达式。
然后如果断言不能满足要求,可以导入第三方的断言库。

动态测试

JUnit Jupiter还引入了一种全新的测试编程模型。
这种新类型的测试是一种动态测试,它是由一个工厂方法在运行时生成的,该方法用@TestFactory注释。与@Test方法相比,@TestFactory方法本身不是测试用例,而是测试用例的工厂。 因此,动态测试是工厂的产物。
同一个@TestFactory所生成的n个动态测试,@BeforeEach和@AfterEach只会在这n个动态测试开始前和结束后各执行一次,不会为每一个单独的动态测试都执行。

 @TestFactoryCollection<DynamicTest> dynamicTestsFromCollection() {return Arrays.asList(dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))),dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2))));}

Mockito的基本使用

Maven导入依赖
<dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>RELEASE</version><scope>test</scope>
</dependency>
基于Mockito的service层测试
import com.demo.entity.User;
import com.demo.service.UserService;
import com.demo.dao.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;@SpringBootTest
public class UserTest {@Mock //将生成MockDao,并注入到@InjectMocks指定的类中private UserRepository userRepository;@InjectMocks //使用Mockito的@InjectMocks注解将待测试的实现类注入private UserService userService;@Testvoid userSelectByName(String userName) {User userMock = new User();userMock.setUserName("张三");when(userRepository.selectByName(any()).thenReturn(userMock);User user = userService.selectByName(userName);assertNotNull(user);}}
controller层测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserTest {@Autowiredprivate TestRestTemplate restTemplate;@Testvoid userSelectByName(String userName) {MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();headers.add("Content-Type", CONTENT_TYPE_TEST);headers.add("Authorization", AUTHORIZATION_TEST);ResponseEntity responseEntity = restTemplate.exchange("/user?name=Thomas", HttpMethod.GET, new HttpEntity<Object>(headers), JSONObject.class);System.out.println(responseEntity.getBody());assertThat(HttpStatus.OK, equalTo(responseEntity.getStatusCode());}}
DAO层测试
 @Autowiredprivate UserRepository userRepository;@Test@Transactional@Rollback(true)// 事务自动回滚,默认是true。可以不写public void insertUser(){User user = new User();assertNotNull(userRepository.insert(user));}
模拟数据生成

JmockData 2.0:https://github.com/jsonzou/jmockdata-demo

测试报告生成

命名规范
  • 测试方法命名
    (1)原方法 or 原方法+Test
    (2) test + 待测场景和期待结果的命名方式
    例如,testDecodeUserTokenSuccess
    (3)“should . … When”结构
    例如,shouldSuccessWhenDecodeUserToken

    • 测试类和方法名称呈现
      @DisplayName
覆盖率统计

cobertura:https://www.jianshu.com/p/159880556d6c

报告美化

《JUnit报告美化——ExtentReports》:https://www.cnblogs.com/goldsking/p/7598085.html

参考资料

[1]《码出高效:Java开发手册》:https://book.douban.com/subject/30333948/
[2] 《JUnit 5 User Guide》:https://junit.org/junit5/docs/current/user-guide
[3] JUnit5用户手册:https://www.cnblogs.com/followerofjests/p/10466070.html
[4] 单元测试实践(SpringCloud+Junit5+Mockito+DataMocker):https://www.cnblogs.com/pluto4596/p/11703382.html
[5] Mockito 中文文档 ( 2.0.26 beta ):https://github.com/hehonghui/mockito-doc-zh

Java单元测试框架与实践(Junit5 + Mockito)相关推荐

  1. 浅谈java单元测试框架junit4/5

    0 前言 junit是一个开源的Java语言的单元测试框架.目前junit主要有版本junit3,junit4和junit5.因在junit3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有 ...

  2. JUnit——Java单元测试框架

    概述 JUnit由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个. 优点 极限编程 基本过程:构思-> 编写测试代码- ...

  3. Mock和Java单元测试中的Mock框架Mockito介绍

    什么是Mock? 在面向对象程序设计中,模拟对象(英语:mock object,也译作模仿对象)是以可控的方式模拟真实对象行为的假的对象.程序员通常创造模拟对象(mock object)来测试其他对象 ...

  4. 高质量的单元测试 Spock单元测试框架详讲

    文章目录 方法篇 为什么需要单元测试 单元测试的定义 单元测试与其他测试的区别 单元测试的作用 关于单元测试的成本 如何写好单元测试 什么场景适合单元测试 单元测试的粒度 关于TDD TDD的三定律 ...

  5. Java单元测试(Junit+Mock+代码覆盖率)

    单元测试是编写测试代码,用来检测特定的.明确的.细颗粒的功能.单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的. 单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复.改进 ...

  6. junit4 单元测试框架_超越JUnit –测试框架的替代方案

    junit4 单元测试框架 JUnit是事实上的Java单元测试框架,但是可能有一些新的(不是那么新的)框架可以用于Web开发. 在采用之前可能要问自己的问题: 它们是否快速,容易开发并因此成本低廉? ...

  7. 转载-使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试

    JUnit 是被广泛应用的 Java 单元测试框架,但是它没有很好的提供参数化测试的支持,很多测试人员不得不把测试数据写在程序里或者通过其它方法实现数据与代码的分离,在后续的修改和维护上有诸多限制和不 ...

  8. Java单元测试实践-09.Mockito的Stub参数条件

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Mocki ...

  9. Java单元测试实践-01.单元测试概述与示例

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. 前言 以下 ...

最新文章

  1. [软件推荐]电子日记本EDiary,记下您 的每一天
  2. mysql调试事件_mysql日志管理分析调试实例_mysql
  3. ITK:将itk :: CovariantVectors的点积
  4. 第8章:Kubernetes 安全
  5. React开发(274):ant design table额外展开行
  6. Esfog_UnityShader教程_NormalMap法线贴图
  7. SAP是如何与外界沟通的?
  8. linux内核分析--异步io(一)
  9. 运动目标跟踪(三)--搜索算法优化搜索方向之Meanshift
  10. SpringBoot添加FastJson消息转换器(自用)
  11. openstack-packstack一键式在线部署
  12. 使用命令结束Linux系统
  13. 教你如何用C/C++做高大上的软件界面
  14. 人数全球第一,但现在中国的问题不是人太多,而是太少 | 浪潮工作室
  15. 要点初见:双硬盘下的Win10+Ubuntu16.04双系统安装
  16. 免费生信课程|多组学数据整合分析之转录组和蛋白质组分析
  17. 深入理解 padding
  18. 深度学习GPU卡的理解(一)
  19. Golang库 - viper读取配置文件
  20. H264解析sps提取宽高(好用)

热门文章

  1. Java--使用httpClient模拟登陆正方教务系统获取课表
  2. 盘点那些用了一次就一直留着的软件 Finalshell、X2GO、geek、IDM
  3. CFA ESG网课资源
  4. vsan服务器系统盘,vSAN群集只显示一个vSAN存储
  5. python入门学习记录之pygame实现简单动画游戏:大球吃小球
  6. ap模式和sta模式共存_无线Wifi图传模块AP和STA工作模式详解
  7. 深入理解卷积神经网络 VGG16
  8. 超级玛丽全通关图文攻略
  9. 自然语言处理(NLP)数据集汇总 4(附下载链接)
  10. 一套手机点餐收银系统源码,系统功能完善、页面美观,开源分享!