SpringBoot2.x系列教程47--SpringBoot中整合测试详细实现步骤
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情
前言
在上一篇文章中,壹哥带大家学习了关于测试的一些内容。接下来在本文中,壹哥会带大家结合SpringBoot,详细的学习如何在Java项目中进行测试。
本文壹哥会从以下4个层面,讲解SpringBoot中的测试功能。
- Service层的单元测试;
- Controller层的单元测试;
- 断言assertThat的使用;
- 单元测试的事务回滚。
一. SpringBoot整合测试功能
1. 创建web项目
我们按照之前的经验,先创建一个Web程序,并将之改造成Spring Boot项目,具体过程略。
2. 添加依赖包
这里添加核心依赖包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3. 创建application.yml配置文件
创建配置文件,进行必要的配置。
server:
port: 8080
spring:
application:
name: spring-test
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: syc
url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&serverTimezone=UTC
jpa:
database: mysql
show-sql: true #开发阶段,打印要执行的sql语句.
hibernate:
ddl-auto: update
4. 创建User实体类
创建一个实体类,封装用户信息。
package com.yyg.boot.entity;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/29
* @Description Description
*/
@Entity
@Table(name="user")
@Data
@ToString
public class User implements Serializable {
@Id
@GeneratedValue(generator = "idGenerator",strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
}
5. 创建UserRepository实体仓库
这里还是像以前那样,利用JPA封装数据库操作。
package com.yyg.boot.repository;
import com.yyg.boot.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/29
* @Description Description
*/
public interface UserRepository extends JpaRepository<User,Long> {
User findByUsername(String username);
}
6. 定义Service层代码
在service层,封装关于用户的数据库操作方法。
6.1 创建UserService接口
package com.yyg.boot.service;
import com.yyg.boot.entity.User;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/29
* @Description Description
*/
public interface UserService {
/**
* save user
*/
User saveUser(User user);
/**
* find user by account
*/
User findByUsername(String username);
}
6.2 定义UserServiceImpl实现类
package com.yyg.boot.service.impl;
import com.yyg.boot.entity.User;
import com.yyg.boot.repository.UserRepository;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/29
* @Description Description
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User saveUser(User user) {
return userRepository.save(user);
}
@Override
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
}
7. 创建Controller接口
创建几个必要的测试接口。
package com.yyg.boot.web;
import com.yyg.boot.entity.User;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/29
* @Description Description
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* save user
*/
@PostMapping(value = "/save")
public User save(@RequestBody User user) {
return userService.saveUser(user);
}
/**
* find user by username
*/
@GetMapping(value = "/{username}")
public User findUser(@PathVariable String username) {
return userService.findByUsername(username);
}
}
8. 创建入口类
最后创建项目的入口类,用于启动项目。
package com.yyg.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/28
* @Description Description
*/
@SpringBootApplication
public class SpringTestApplication {
public static void main(String[] args){
SpringApplication.run(SpringTestApplication.class,args);
}
}
二. 创建Service层的测试类
接下来我们就要开始进行测试了,这里先对service层的代码进行功能测试。
1. 自动创建测试类
Spring Boot中的单元测试类一般都要写在src/test/java目录下,并且测试类路径应该与要测试的类路径一直。
我们可以手动创建具的体测试类,但是在IDEA这样比较智能的开发工具中,我们其实可以通过IDEA工具自动创建测试类。
也可以通过快捷键⇧⌘T(MAC)或者Ctrl+Shift+T(Window)来创建。
我们只需要把光标定位在要测试的类中任意位置,然后通过点击Navigate-->Test按钮,或者通过快捷键,IDEA就会自动帮我们在测试目录下创建出一个对应的测试类。如下图所示:
可以勾选要进行测试的方法,也可以自动产生@Before与@After等测试方法。
2. 测试类内容
默认情况下,会产生如下所示的测试类:
package com.yyg.boot.service.impl;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/7
* @Description Description
*/
class UserServiceImplTest {
@Test
void saveUser() {
}
@Test
void findByUsername() {
}
}
我们只需要在该测试类上添加@RunWith(SpringRunner.class)和@SpringBootTest注解就可以了。
3. UserServiceImplTest的具体实现
我们编写一个UserServiceImplTest测试类,在这里封装测试脚本。
package com.yyg.boot.service.impl;
import com.yyg.boot.entity.User;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.hamcrest.CoreMatchers.*;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/7
* @Description Description
*/
@RunWith(SpringRunner.class)
@SpringBootTest
//注意:当添加@Transactional事务注解之后,对数据库的增/删/改操作数据,不会被真正存储到数据库中,因为事务没有被提交,可以防止测试数据污染真实数据.
//@Transactional
//@Rollback(true)
class UserServiceImplTest {
@Autowired
private UserServiceImpl userService;
//利用@Rollback注解,对数据库进行增删改时是否进行回滚,默认是执行回滚操作.
//@Rollback(true)
@Test
void saveUser() {
User user=new User();
user.setUsername("test2");
user.setPassword("123");
User result = userService.saveUser(user);
Assert.assertThat(result,notNullValue());
}
@Test
void findByUsername() {
User user = userService.findByUsername("tom");
Assert.assertThat(user.getUsername(),is(equalTo("tom")));
}
}
4. service方法具体测试
我们只需要把光标定位在要测试的方法内部,然后右键选择run执行就可以了。
一开始数据库中的数据如下:
测试成功的效果:
此时数据库中的数据如下所示:
查询方法的测试效果如下:
当我们测试失败时的效果如下所示:
这样我们就把service层的代码测试完毕。
三. 进行Controller层的测试
接下来壹哥再给大家讲解controller层的代码该怎么进行测试。
1. 自动创建Controller层的测试类
这里壹哥先创建出controller层的代码测试类,我们直接利用IDE工具自动创建即可。
2. 构建出MockMvc对象
这里我们利用MockMvc进行测试。
package com.yyg.boot.web;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/7
* @Description Description
*/
@RunWith(SpringRunner.class)
@SpringBootTest
//自动配置mock
@AutoConfigureMockMvc
class UserControllerTest {
//创建出一个模拟的MVC对象
@Autowired
private MockMvc mvc;
@Test
public void save() throws Exception {
}
@Test
public void findUser() throws Exception {
}
}
3. 具体测试代码实现
具体的测试代码如下所示:
package com.yyg.boot.web;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author 一一哥Sun
* @Date Created in 2020/5/7
* @Description Description
*/
@RunWith(SpringRunner.class)
@SpringBootTest
//配置mock
@AutoConfigureMockMvc
class UserControllerTest {
//模拟一个MVC对象
@Autowired
private MockMvc mvc;
@Test
public void save() throws Exception {
String json = "{"username":"test3","password":"123"}";
mvc.perform(MockMvcRequestBuilders.post("/user/save")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
//传json参数
.content(json))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
@Test
public void findUser() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/user/test2")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").value("test2"))
.andExpect(MockMvcResultMatchers.jsonPath("$.password").value("123"))
.andDo(MockMvcResultHandlers.print());
}
}
4. MockMvc执行解释
上面代码中涉及到不少新内容,壹哥给大家简单解释一下。
- mockMvc.perform执行一个请求;
- MockMvcRequestBuilders.get(“/user/save”)构造一个请求,Post请求就用.post方法;
- contentType(MediaType.APPLICATION_JSON)代表发送端发送的数据格式是application/json;
- accept(MediaType.APPLICATION_JSON)代表客户端希望接受的数据类型为application/json;
- ResultActions.andExpect添加执行完成后的断言;
- ResultActions.andExpect(MockMvcResultMatchers.status().isOk())方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过;
- andExpect(MockMvcResultMatchers.jsonPath("$.username").value("test2"))这里jsonPath用来获取username字段对比是否为”test2“,不是则测试不通过;
- ResultActions.andDo添加一个结果处理器,表示要对结果做什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
5. 完整项目结构
此时完整的项目结构如下图所示,大家可以参考创建。
四. 断言assertThat的使用
1. assertThat断言简介
从JUnit4.4版本开始,结合 Hamcrest,提供了一个全新的断言语法——assertThat。
我们可以只使用一个assertThat断言语句,结合 Hamcrest 提供的匹配符,就可以表达全部的测试思想。
2. assertThat 的基本语法
assertThat( [value], [matcher statement] );
#value 是接下来想要测试的变量值;
#matcher statement 是使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明,
#如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。
3. assertThat 的优点
- 优点 1: 以前 JUnit 提供了很多的 assertion 语句,如:assertEquals,assertNotSame,assertFalse,assertTrue,assertNotNull,assertNull 等,现在有了assertThat,即可以替代所有的 assertion 语句,使得编写测试用例变得简单,代码风格变得统一,测试代码也更容易维护。
- 优点 2: assertThat 使用了 Hamcrest 的 Matcher 匹配符,用户可以使用匹配符规定的匹配准则精确的指定一些想要设定满足的条件,具有很强的易读性,而且使用起来更加灵活。
- 优点 3: assertThat 不再像 assertEquals 那样,使用比较难懂的“谓宾主”语法模式(如:assertEquals(3, x);),相反,assertThat 使用了类似于“主谓宾”的易读语法模式(如:assertThat(x,is(3));),使得代码更加直观、易读。
- 优点 4: 可以将这些 Matcher 匹配符联合起来灵活使用,达到更多目的。
4. 断言中的Matcher匹配符
五. 单元测试的事务回滚
我们在进行单元测试的时候,如果不想让测试数据对正式数据库,造成垃圾数据,可以开启事务功能。只需要在方法或者类上面添加@Transactional注解即可。
@RunWith(SpringRunner.class)
@SpringBootTest
//注意:当添加@Transactional事务注解之后,对数据库的增/删/改操作数据,不会被真正存储到数据库中,因为事务没有被提交,可以防止测试数据污染真实数据.
@Transactional
//@Rollback(true)
class UserServiceImplTest {
@Autowired
private UserServiceImpl userService;
//利用@Rollback注解,对数据库进行增删改时是否进行回滚,默认是执行回滚操作.
//@Transactional
//@Rollback(true)
@Test
void saveUser() {
User user=new User();
user.setUsername("test2");
user.setPassword("123");
User result = userService.saveUser(user);
Assert.assertThat(result,notNullValue());
}
@Test
void findByUsername() {
User user = userService.findByUsername("tom");
Assert.assertThat(user.getUsername(),is(equalTo("tom")));
}
}
这样测试完数据就会回滚了,不会造成垃圾数据。如果你想关闭事务的回滚功能,要么不添加@Transactional注解,要么可以加上@Rollback(false)注解。
@Rollback表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。
但是如果你使用的数据库是Mysql,有时候会发现加了注解@Transactional 也不会回滚,那么你就要查看一下你的默认引擎是不是InnoDB,如果不是就要改成InnoDB。
结语
至此,壹哥 就给大家详细的讲解了如何在SpringBoot中整合测试功能,大家可以参考本文实现一下,你学会了吗?有什么问题,可以在评论区给壹哥留言哦!
转载自:https://juejin.cn/post/7175678102331195452