SpringBoot单元测试的两种形式
@
前言
最近公司要求2021年所有的项目代码单元测试覆盖率要达到90%,作为刚毕业的小白来说这简直就是噩梦啊,springboot都没搞清楚呢,就要上手单元测试了。组里大佬说,单元测试有下面的各种好处:
- 发现逻辑中遗漏的数据结构及粗心错误
- 发现代码逻辑中90%可能会发生但是容易被忽略的NPE错误
- 检测代码逻辑是否能正常运行
- 检测代码结果是否符合预期
- 发现其他错误
既然领导和大佬都这么说了,小白只能突击学习单元测试了!当然,单元测试是一个开发人员必备的技能,不仅能够提升自身的开发能力,也可以提升自身的BUG改正能力。
Junit是目前使用最广泛、最多的单元测试工具类,IDEA在创建了springboot项目之后,可以自动集成Junit,可以通过创建单元测试类来测试。
SpringbootTest是集成于Springboot中的一个单元测试工具,它可以使开发人员在测试各种接口、业务代码时不用关心外部依赖是如何注入的,只需要关心代码本身。
本文具体就展现这两种测试方式有何不同,以及总结一些个人的观点。
由于项目中有比较多的业务代码,所以特地写了一个demo,里面的代码也是能省则省,之展现了单元测试具体的表现形式,逻辑方面还请大家多担待!
demo环境
使用的springboot项目,junit4,因为懒所以用的jpa没有用mybatis,但是用法还是那个用法。测试的项目数据库中没有数据。项目分为controller、dao、entity、service,下面是各层的代码,按介绍顺序放出各层对应的demo:
controller:
/**
* 创建时间:2021/1/7 11:32
* 单元测试控制类
* @author wyb
*/
@RestController
@RequestMapping("/student")
public class TestDemoController {
@Resource
private TestDemoService testDemoService;
@GetMapping("/list")
public List<Student> list(StudentQuery studentQuery){
return testDemoService.isExist(studentQuery);
}
}
dao:
/**
* 创建时间:2021/1/7 11:38
*
* @author wyb
*/
@Repository
public interface StudentProperty extends JpaRepository<Student,Integer> {
/**
* 根据姓名模糊查找student
* @param name 姓名
* @return
*/
List<Student> findByNameLike(String name);
}
entity:
@Entity
@Table(name="student")
public class Student {
@Id
private Integer uid;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
@Column(name = "remarks")
private String remarks;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
@Override
public String toString() {
return "Student{" +
"uid=" + uid +
", name='" + name + '\'' +
", age=" + age +
", remarks='" + remarks + '\'' +
'}';
}
}
service:
/**
* 创建时间:2021/1/7 11:33
* 单元测试类service
* @author wyb
*/
@Service
public class TestDemoService {
@Resource
private StudentProperty studentProperty;
/**
* 查询学生列表
* @return
*/
public List<Student> listStudent(){
//查询学生列表
//简单的逻辑
return studentProperty.findAll();
}
public List<Student> isExist(StudentQuery studentQuery){
Student student = new Student();
if(!Objects.isNull(studentQuery)){
student.setName(studentQuery.getName());
}
return studentProperty.findByNameLike(studentQuery.getName());
}
}
同时为了方便,我还写了一个student的查询类
/**
* 创建时间:2021/1/7 11:48
*
* @author wyb
*/
public class StudentQuery {
/**
* 学生姓名
*/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "StudentQuery{" +
"name='" + name + '\'' +
'}';
}
}
springbootTest
因为是springboot中的单元测试,所以先讲SpringbootTest吧!
建立test类之后,在类上加上注解
@SpringBootTest
@RunWith(SpringRunner.class)
用此以表示该类在运行测试时使用SpringbootTest的形式运行。该类让测试类在运行时直接运行整个项目框架,换句话说,他会去寻找application启动类,运行整个项目后运行测试代码,单元测试代码就是整个项目中的一部分。
该测试注解可以在任何测试类中添加,controller、service、mapper、dao都可以添加该注释,这里使用controller给大家演示:
@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@WebAppConfiguration
public class TestDemoControllerTest {
@Autowired
private TestDemoController testDemoController;
@Autowired
private MockMvc mockMvc;
@Test
public void list() {
//通过注入的方式直接测试controller中list方法
StudentQuery studentQuery = new StudentQuery();
studentQuery.setName("xx");
List<Student> studentList = testDemoController.list(studentQuery);
System.out.println(studentList);
}
@Test
public void listByWeb() throws Exception {
//通过接口的方式测试接口
this.mockMvc.perform(get("/student/list")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("name","xx"))
.andReturn();
}
}
使用常规的Autowired注入controller,类中可直接调用controller(类似于service调用),这样的话可以直接测试controller中的代码逻辑,但会启动整个springboot项目。第二个测试方式是通过webMvc的形式,以解控调用的环境测试controller,使用的是mockMvc,其在Mock工具类中。该工具类模拟了各种接口调用方式,这里只展现了get方法的调用方式。
list方法测试通过结果:

listByWeb方法测试通过结果:

可以看出,两种方法不一样的只是调用方式,结果上都是一样的。
那为什么会有两种调用方式?看最后!
Junit
Junit的测试,更注重于代码逻辑,这里进行的单元测试不能识别框架是否能启动,只能识别测试的代码逻辑能否通过,我使用了service进行junit单元测试:
public class TestDemoServiceTest {
@Test
public void listStudent() {
TestDemoService testDemoService = new TestDemoService();
StudentProperty studentProperty = mock(StudentProperty.class);
// 构建对象中指定的字段属性
ReflectionTestUtils.setField(testDemoService,"studentProperty", studentProperty);
when(studentProperty.findAll()).thenReturn(null);
List<Student> studentList = testDemoService.listStudent();
//断言不可能为null
//Assert.assertNotNull(studentList);
System.out.println(studentList);
}
@Test
public void isExist() {
}
}
这里使用了原生的junit,同时也使用了mock工具类,它可以解决在原生单元测试中外部依赖无法引入的问题,同时也可以模拟各项参数以及返回数据,可以测试数据为空,参数为空等场景。
这样测试的好处是只关注该方法的逻辑正确性,并且不在数据库中添加多余的脏数据(甚至不会链接数据库)。例如
// 构建对象中指定的字段属性
ReflectionTestUtils.setField(testDemoService,"studentProperty", studentProperty);
when(studentProperty.findAll()).thenReturn(null);
这里就模仿了dao层查询数据后可能会返回的数据,我们可以指定返回什么数据。
每一次返回数据后,请多用assert断言,不符合断言的数据会立即报错,这样会解决很多空指针错误。
更多的mock用法请参考官网mokito
总结
springbootTest和原生的junit各有各的好,我们刚刚完成的项目中单元测试就同时使用了这两种测试。
在service中我们使用junit测试保证代码逻辑的正确性,同时在controller层使用spring boot保证接口的可用性及接口方法逻辑的正确性,同时也确保了在引入外部依赖后框架依然能够正常启动。
前文提到在测试controller中可以用注入,也可以模拟接口,为什么这么做呢,因为直接注入之后无法确定该接口是否能够正常被外部调用,如果传参不对是否会报错,报错是否需要处理,我们无法得知,所以需要一个模拟环境去测试该接口的可用性!
直接注入则是测试从接口到数据库是否是正常的,但前提是service的单元测试已经完成并通过。
SpringBoot单元测试的两种形式的更多相关文章
- C++:一般情况下,设计函数的形参只需要两种形式
C++:一般情况下,设计函数的形参只需要两种形式.一,是引用形参,例如 void function (int &p_para):二,是常量引用形参,例如 void function(const ...
- jquery插件的两种形式
这里总结一下jquery插件的两种形式,一种是通过字面量的形式组织代码,另一种是通过构造函数的方式.下面就两种形式来分析俩个例子. 例子1: ;(function ($,window,document ...
- SQL 关于apply的两种形式cross apply 和 outer apply(转)
转载链接:http://www.cnblogs.com/shuangnet/archive/2013/04/02/2995798.html apply有两种形式: cross apply 和 oute ...
- SQL 关于apply的两种形式cross apply 和 outer apply
SQL 关于apply的两种形式cross apply 和 outer apply 例子: CREATE TABLE [dbo].[Customers]( ) COLLATE Chinese_PRC_ ...
- SQL关于apply的两种形式cross apply和outer apply(转载)
SQL 关于apply的两种形式cross apply 和 outer apply apply有两种形式: cross apply 和 outer apply 先看看语法: <lef ...
- 在 Perl看来, 字符串只有两种形式. 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串, perl管它叫string. 也就是说: Perl只熟悉两种编
在 Perl看来, 字符串只有两种形式. 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串, perl管它叫string. 也就是说: Perl只熟悉两种编 ...
- Controller@实现Controller的两种形式
实现Controller的两种形式 形式1:仅仅实现IController接口,自定义Controller对Request的实现.形式2:在实现IController接口以后,继承Controller ...
- 在sql中case子句的两种形式
case子句,在select后面可以进行逻辑判断. 两种形式:判断相等.判断不等 一.判断相等的语法: case 列名 when ... then ... when ... then ... el ...
- 转:SQL 关于apply的两种形式cross apply 和 outer apply
原文地址:http://www.cnblogs.com/Leo_wl/archive/2013/04/02/2997012.html SQL 关于apply的两种形式cross apply 和 out ...
随机推荐
- 一步步分析:C语言如何面向对象编程
这是道哥的第009篇原创 一.前言 在嵌入式开发中,C/C++语言是使用最普及的,在C++11版本之前,它们的语法是比较相似的,只不过C++提供了面向对象的编程方式. 虽然C++语言是从C语言发展而来 ...
- kepler.gl 2.4.0重要更新
1 简介 kepler.gl作为开源地理空间数据可视化神器,也一直处于活跃的迭代开发状态下.而在前不久,kepler.gl正式发布了其2.4.0版本,下面我们就来对其重要的新特性进行介绍: 图1 2 ...
- 2020 .NET 开发者峰会顺利在苏州落幕,相关数据很喜人以及线上直播回看汇总
在2019年上海中国.NET开发者大会的基础上,2020年12月19-20日 继续以"开源.共享.创新" 为主题的第二届中国 .NET 开发者峰会(.NET Conf China ...
- Python Cvxopt安装及LP求解
Python 2.7 Pycharm 1.直接File>Settings>Project>InterPreter ,点击右侧'+' 弹出Available packages窗口,搜索 ...
- CI/CD自动化发版系统设计简介
转载自:https://www.cnblogs.com/wellful/archive/2004/01/13/10604151.html 版本迭代是每一个互联网公司必须经历的,尤其是中小型公司,相信不 ...
- LINQ to Entities 不识别方法“System.String ToString(“yyyy-MM-dd”)”
将Queryable转化为IEnumerable或者直接Tolist()
- 又到期末了,为什么学完C语言觉得好像没学一般?复习资料来一份
不少同学从Hello world学到文件操作之后,回顾感觉会又不会? 学会了又感觉没学会?这种不踏实.模糊虚无的感觉? 原因在于编程不同于理论学科,你听懂和理解了理论就可以运用,比如历史地理,看完书, ...
- win8.1下jdk的安装和环境变量的配置 eclipse的安装和汉化
1.首先下载jdk安装包,安装的时候会有两个文件安装,一个是jdk一个是jre建议两个文件不要安装在一个目录下 2.安装jdk后面就是配置环境变量,path和classpath,path要在用户变量中 ...
- 数据库索引的基石----B树
数据结构相对来说比较枯燥, 我尽量用最易懂的话,来把B树讲清楚.学过数据结构的人都接触过一个概念二叉树,简单来说,就是每个父节点最多有两个子节点.为了在二叉树上更快的进行元素的查找,人们通过不断的改进 ...
- Vue3.0聊天室|vue3+vant3仿微信聊天实例|vue3.x仿微信app界面
一.项目简介 基于Vue3.0+Vant3.x+Vuex4.x+Vue-router4+V3Popup等技术开发实现的仿微信手机App聊天实例项目Vue3-Chatroom.实现了发送图文表情消息/g ...