Junit+Jacoco+SonarQube实现单元测试及覆盖率
feilongw 2025-01-18 19:09 2 浏览 0 评论
一、为什么要做单元测试
一个程序是由许多基本单元代码组合而成复杂的系统,如果程序的基本单元都无法保证正确性,代码层级递增时,错误就会不断放大,直到整个系统无法使用。所以单元测试的意义就在于保证基本代码模块正常稳定,减少并快速定位bug,提高代码质量,提升集成测试和系统测试效率。
二、环境准备
1. 搭建一个实现注册和登录得springboot项目,仓库地址:
GitHub - loading0/UnitTestDemo: JunitTestDemo
项目使用请查看仓库中的readme
2. 测试-pom相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.9</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>3.0.0-M4</version>
</dependency>
其中junit是包含在spring-boot-start-test依赖中,所以不用手动添加,点击"pring-boot-starter-test"跳转可以查看到其依赖包含的包:包含了junit和mockito。
三、单元测试框架-Junit
1. 这里使用spring-boot-start-test版本为2.7.10,依赖的是junit5
在使用junit4的时候测试单元类写法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class XXXTest{}
对于上面的RunWith注解,JUnit5官方文档的说法如下图红框所示,已经被ExtendWith取代, 且SpringBootTest注解中已包含ExtendWith,因此SpringBoot+JUnit5时,RunWith注解已经不需要了。如下即可:
@SpringBootTest
class UserServiceImplTest {
@Autowired
UserService userService;
2. Junit5中常用的注解
2.1 测试方法:
测试类中被@Test、@ParameterizedTest、@RepeatedTest、@TestFactory等装饰的方法都是测试方法。
2.2 其它注解
主要包括BeforeAll、BeforeEach、AfterAll、AfterEach、Timeout、Displayed等。
四、单元测试编写实现
1. 生成对应测试类
在源代码要实现单元测试的类名上右键goto->test,自动在test对应目录生成相关类的测试类文件
2. 参数化测试(@ParameterizedTest)
测试数据最好与代码进行分开,增强代码可读性和维护性,其中数据源的种类较多,主要使用的如下几种:
- 方法数据源(@MethodSource)
- 取值数据源(@ValueSource)
- 枚举数据源(@EnumSource)
- CSV数据源(@CsvSource)
- CSV文件数据源 (@CsvFileSource)
- null和空字符串数据源(@NullAndEmptySource)
- 本例以methodSource进行数据参数化实现 UserServiceImpl中的login函数的单元测试,其它方式可以参考官方文档使用
/** 用户登录成功的案例 */
@ParameterizedTest
@MethodSource("loginData")
void loginTest(User userLoginData){
User userInfo = userService.login(userLoginData);
Assertions.assertEquals(userLoginData.getUserAccount(),userInfo.getUserAccount());
}
/** 用户登录成功案例的数据源 */
static Stream loginData(){
ArrayList<User> userList = new ArrayList<>();
User u1 = new User();
User u2 = new User();
u1.setUserAccount("admin");
u1.setPassword("123456");
u2.setUserAccount("root");
u2.setPassword("123456");
userList.add(u1);
userList.add(u2);
return Stream.of(u1, u2);
}
上面的stringProvider方法和测试方法methodSourceTest在同一个类中,如果它们不在同一个类中,就要指定静态方法的整个package路径、类名、方法名,类名和方法名之间用#连接。
执行结果:
3. 异常测试
同业务测试一样,既要保障正确性流程测试,也需要进行异常性测试,才能提高系统的容错性。单元测试异常性测试首先要对业务中捕获异常的代码进行覆盖测试,其次设计更多可能性的异常案例进行测试。
3.1 异常测试使用Assertions.assertThrows去断言该情况会抛出指定的异常,本例中是登录时用户名和密码错误,抛出自定义异常UsernameOrPasswordException
/** 用户登录发生异常的测试案例 */
@Test
void loginException() {
User u1 = new User();
u1.setUserAccount("admint");
u1.setPassword("123456");
Assertions.assertThrows(UsernameOrPasswordException.class, ()->{
userService.login(u1);
});
}
运行结果:
3.2 以下是用户注册时名称重复,抛出UsernameDuplicatedException异常
/** 用户注册发生异常的测试案例 */
@Test
void registerException() {
User u1 = new User();
u1.setUserAccount("admin");
u1.setPassword("123456");
Assertions.assertThrows(UsernameDuplicatedException.class, ()->{
userService.register(u1);
});
}
运行结果:
4. Controller类测试
除了service层的测试,很多数据处理方面也是在controller,故也需要对controller层进行测试,由于需要模拟request,再次使用MockMvc进行请求的模拟。
4.1 在添加请求数据有两种方式,一种使用param.add(),每次添加单个参数;另一种是将所有请求数据添加到json字符串中,通过.content()加入。
4.2 本例使用了csvFileSource数据源格式,文件格式如下,其中numLinesToSkip是忽略首行字段项名称。
account,password,username
tester1,pass1,username1
tester2,pass2,username2
tester3,pass3,username3
4.3 @Rollback @Transcational用来进行数据库数据回滚操作,减少测试数据对一些测试影响。
4.4 代码如下:
@ParameterizedTest
@CsvFileSource(files="src/test/resources/register.csv",numLinesToSkip=1)
// @Rollback(value = true)
// @Transactional
void register(String account, String password, String username) {
MvcResult mvcResult = null;
// www-form-urlencode格式传送参数,contentType = APPLICATION_FORM_URLENCODED
// MultiValueMap param = new LinkedMultiValueMap();
// param.add("userAccount","abc");
// param.add("password","123456");
// param.add("userName", "tester");
User user = new User();
user.setUserAccount(account);
user.setPassword(password);
user.setUserName(username);
String jsonStrUser = JSON.toJSONString(user);
System.out.println(jsonStrUser);
try{
mockMvc.perform(MockMvcRequestBuilders.post("/user/register")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
// .params(param))
.content(jsonStrUser))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("注册成功")));
}catch (Exception e){
e.printStackTrace();
}
}
执行结果:
用户已注册成功,将用户数据添加到数据库表中
五、jenkins集成
单元测试是测试左移策略中一个很重要的环节,可以极快的发现问题并解决。单元测试一个应用场景是开发在业务代码实现过程中任务手动触发测试,把问题在开发内部化解掉;另一个应用场景是在各种环境集成过程中通过webhook自动触发jenkins执行。
5.1 环境准备
jenkins-->plugin Manager中搜索jacoco plugin 和SonarQube Scanner进行安装
并在configure system中配置sonarqube Servers
5.2 jenkins job配置
- 源码管理
添加配置源码仓库地址、凭证。前提是已配置凭证,或者点击“+添加” 现场配置。
- 构建触发
通过webhook方式构建,前提是在gitlab上已配置好webhook。
- 构建
添加构建步骤:excute shell,clean test只执行单元测试,不涉及打包操作。
关于package、install、deploy的区别如下:
mvn package命令完成了项目编译、单元测试、打包功能,但没有把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库
mvn install命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库,但没有布署到远程maven私服仓库
mvn deploy命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库
添加构建操作:Execute SonarQube Scanner,用于代码静态扫描,及集成jacoco报告文件用于展示代码覆盖率。前提是服务器上已搭建并启动sonarqube服务,同时jenkins上已配置sonarqube servers
添加构建后操作:Publish JUnit test result report,用于单元测试结果统计和展示,非代码覆盖率结果是测试案例执行结果。
添加构后操作:Record JaCoCo coverage report,用于单元测试结果的覆盖率统计和展示。
六、jenkins任务执行
6.1 提交代码到gitlab仓库
触发webhook执行jenkins任务
6.2 执行console log
触发开始执行任务
从gitlab拉取代码
执行单元测试
最终结果
6.3 任务构建结果输出
jenkins端单元测试结果:
展示了案例的失败,跳过,通过以及总数的统计,点击跳转查看详情。
jenkins端单元测试覆盖结果:
上方图像时测试结果通过了趋势,下方图像展示代码覆盖率趋势,这样可以清晰的掌握代码测试执行效果,用于对比和总结。
其中code Coverage Trend点击可以查看具体的代码覆盖情况
SonarQube server端结果展示:
这里用之前一个项目图来代替展示单元测试前及测试后的sonarqube分析对比。
6.4 邮件通知
任务构建后的邮件通知或飞书、钉钉通知可以参考微信公众号(ATester)之前两篇文章配置
《UI自动化测试-UIRecoder录制回放测试》
《Python之工作群消息发送 | 飞书,钉钉》
七、总结
单元测试是测试人员介入测试执行的第一个环节,也是最接近业务代码的测试。单元测试执行的成功率和覆盖率不一定与软件质量成正比,但可以以特定的方式去保障软件的质量,去提高业务测试效率,降低敏捷开发中版本迭代成本。
在实际的CI/CD中,单元测试只是作为一个前提条件,更多的是进行打包后,docker image构建,上传;以及服务器获取jar/war包或者拉取image,然后部署并启动服务。
感谢阅读,欢迎关注微信公众号(ATester),所有文章会在公众号首发。
相关推荐
- win7如何录屏?手把手教你怎么录制电脑屏幕
-
现在大部分人的电脑是win10系统,但仍有小伙伴的电脑是win7电脑,有时候也需要对电脑屏幕进行录制。那你知道win7如何录屏吗?虽然win7电脑不像win10电脑有自带的录屏工具XboxGame...
- Windows系统必会的八个快捷键,让你效率提升数倍
-
前言:当电脑开启十几个窗口,你还在一个一个的最小化窗口时,别人可以瞬间回到电脑桌面;当电脑开启十几个窗口,你还在一个一个的最小化窗口再试图打开[我的电脑]窗口时,别人在你还没有最小化第一个窗口时已经打...
- 7个超实用的电脑截图快捷键
-
大家在截图时,最常用的就是QQ的截图方法。QQ可以说是聊天软件里截图功能做得最好的,截图软件里聊天功能做得最好的。其截图功能既简单又好用,只需要一个快捷键「Ctrl+Alt+A」。确定好截图窗口,鼠...
- Windos快捷键
-
办公软件中的快捷键我们了解不少,关于Windows系统的操作快捷键你知道几个呢?一、【Win+D】:快速切换到系统桌面(再按一下就可以返回到之前的页面)二、【Win+M】:最小化所有窗口(此组合键无法...
- 电脑Win键失灵没反应怎么修复?3步优化轻松修复
-
最近突然发现按键盘上的“Win键”失灵了,没有任何反应。这么一来很多和Win键相关的组合快捷键都使用不了了,比如:Win+R,Win+S,Win+I,Win+E,Win+Q。是我键盘坏了吗?我换了别的...
- Win7电脑的5大截屏方式,总有你不知道的!
-
有时需要将电脑屏幕上的内容截取下来,该怎么操作呢?或许你会用QQ截图,但其实windows系统已经自带了截图哦,今天我就为大家说道说道。1、按Prtsc键这样获取的是整个电脑屏幕的内容,可直接粘贴使用...
- Windows快捷键大全
-
一、常见用法:F1 显示当前程序或者windows的帮助内容。F2 当你选中一个文件的话,这意味着“重命名”F3 当你在桌面上的时候是打开“...
- win7截图快捷键有什么 win7自带截图快捷键怎么用
-
win7截图快捷键有什么?作为经典的操作系统版本,Windows7仍然被许多用户广泛使用。大家对Windows7系统的各项功能已经非常清楚,就像屏幕截屏的快捷键一样,大家也都相当熟悉。不过你是否知...
- win7音量调节窗口显示步骤
-
用户反馈在使用音量调节时,点击音量调节图标发现调节窗口不见了,下面给大家介绍显示win7音量调节窗口步骤。 1、同时按下快捷键“win+r”打开运行窗口,输入“regedit”命令并回车,如下图所示...
- Win7电脑如何快速截图
-
日常工作中经常需要截图,都都要借助第三方软件来截图,下面说一下Win7电脑全屏截图及部分截图的方法。将键盘的屏幕截图按钮PrScrn按钮,按PrScrn按钮截取整个屏幕,Ctrl+PrScr...
- 系统小技巧:为软件让路 关闭系统Win+快捷键
-
一些软件使用Win键加某个键作为快捷键来调用某个功能,但有时软件中所用快捷键与系统快捷键发生冲突,造成软件快捷键无法使用,尤其是早期开发的软件与Windows10之间的这种冲突比较普遍。如果暂时关闭...
- Win7电脑怎么快速锁屏?一个锁屏快捷键,帮你轻松锁定防偷看
-
平时我们在使用Win7系统电脑来进行学习或者是工作时,我们往往会因为有会议要开,或者是有事情要做,从而需要离开电脑。但是如果此时电脑上正在编辑很重要的文档,那么此时让电脑锁屏,不仅可以防止我们的隐...
- 最佳Windows 11键盘快捷键列表
-
想用键盘在Windows11上运行吗?以下是使用快捷方式在Windows11中执行常见任务的方法。尽管Windows10中支持的几乎所有快捷方式在Windows11中都保持不变,但微软古老...
- 五分钟包学会 笔记本装Win8.1详尽攻略
-
|责编:王冬奇经过半年多的等待,我们终于迎来了Windows8.1系统的2014UPDATE1升级。在此之前Windows8和Windows7相比引入了更简单直观、易于操作的WindowsU...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- excel换行 (95)
- 虚拟机安装 (78)
- web服务器安全配置 (77)
- excel2007视频教程 (82)
- 右键菜单恢复 (93)
- 启动项命令 (81)
- excel打开空白 (83)
- phpmyadmin 下载 (75)
- inventor教程 (81)
- flv文件播放 (83)
- tomcat 7 0 (82)
- 服务器正在运行中 (100)
- m3u8 (89)
- 矢量图是什么 (79)
- 静态ip怎么设置 (82)
- windows update是什么意思 (86)
- 文件切割 (77)
- filezilla_server (79)
- 裁切图片 (76)
- rank函数 (76)
- 网络地址分类 (79)
- 二维码转链接在线生成 (97)
- 字体在哪个文件夹 (78)
- solidworks工程图模板 (82)
- securecrt (77)