意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

开发实用篇(二)

来源:恒创科技 编辑:恒创科技编辑部
2024-02-03 17:24:59


开发实用篇(二)测试加载测试专用属性

在启动测试环境时可以通过 properties 参数设置测试环境专用的属性

@SpringBootTest(properties = {"test.prop=testValue1"})
public class PropertiesAndArgsTest {

@Value("${test.prop}")
private String msg;

@Test
void testProperties() {
System.out.println("msg = " + msg);
}

}

优势:比多环境开发中的测试环境影响范围更小,仅对当前测试类有效


开发实用篇(二)

在启动测试环境时可以通过args参数设置测试环境专用的传入参数

// args属性可以为当前测试用例添加临时的命令行参数
@SpringBootTest(args = {"--test.prop=testValue2"})
public class PropertiesAndArgsTest {

@Value("${test.prop}")
private String msg;

@Test
void testProperties() {
System.out.println("msg = " + msg);
}

}

当 args参数 与 properties参数 设置共存时, args属性配置优先于properties属性配置加载。

小结

加载测试临时属性应用于小范围测试环境加载测试专用配置

步骤①:在测试包test中创建专用的测试环境配置类

@Configuration
public class MsgConfig {

@Bean
public String msg() {
return "bean msg";
}

}

上述配置仅用于演示当前实验效果,实际开发可不能这么注入String类型的数据

步骤②:在启动测试环境时,导入测试环境专用的配置类,使用 ​​@Import​​ 注解即可实现

@SpringBootTest
@Import({MsgConfig.class})
public class ConfigurationTest {

@Autowired
private String msg;

@Test
void testConfiguration() {
System.out.println("msg = " + msg);
}

}

小结

加载测试范围配置应用于小范围测试环境Web环境模拟测试

模拟端口

每一个springboot的测试类上方都会标准@SpringBootTest注解,而注解带有一个属性,叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境,具体如下:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {

@Test
void test() {

}

}
测试类中启动web环境时,可以指定启动的Web环境对应的端口,springboot提供了4种设置值,分别如下:

开发实用篇(二)_spring

MOCK:根据当前设置确认是否启动web环境,例如使用了Servlet的API就启动web环境,属于适配性的配置DEFINED_PORT:使用自定义的端口作为web服务器端口RANDOM_PORT:使用随机端口作为web服务器端口NONE:不启动web环境虚拟请求测试新建​​BookController​​ 类
@RestController
@RequestMapping("/books")
public class BookController {

@GetMapping
public String getById() {
System.out.println("getById is running... ");
return "springboot";
}

}
虚拟请求测试
// 1.使用 webEnvironment 属性开启web环境
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 2.开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {

// 注入虚拟MVC调用对象
@Test
void testWeb(@Autowired MockMvc mvc) throws Exception {
// http://localhost:8080/books
// 3.创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
// 执行对应的请求
mvc.perform(builder);
}

}
匹配响应执行状态

虚拟请求状态匹配

// 注入虚拟mvc调用对象
@Test
void testStatus(@Autowired MockMvc mvc) throws Exception {
// 创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
// 执行请求
ResultActions actions = mvc.perform(builder);
// 设定预期值 与真实值进行比较;成功测试通过,失败测试失败
// 定义本次调用的预期值
StatusResultMatchers status = MockMvcResultMatchers.status();
// 预计本次调用时成功的:状态200
ResultMatcher ok = status.isOk();
// 添加预计值到本次调用过程中进行匹配
actions.andExpect(ok);
}
匹配失败的信息提示如:

开发实用篇(二)_java_02

匹配响应体

响应体匹配(非json数据格式)

// 注入虚拟mvc调用对象
@Test
void testBody(@Autowired MockMvc mvc) throws Exception {
// 创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
// 执行请求
ResultActions actions = mvc.perform(builder);
// 设定预期值 与真实值进行比较;成功测试通过,失败测试失败
// 定义本次调用的预期值
ContentResultMatchers content = MockMvcResultMatchers.content();
ResultMatcher result = content.string("springboot");
// 添加预计值到本次调用过程中进行匹配
actions.andExpect(result);
}
匹配失败的信息提示如:

开发实用篇(二)_spring boot_03

匹配响应体(json)

响应体匹配(json数据格式,开发中的主流使用方式)

创建 book 实体类
@Data
public class Book {

private int id;
private String name;
private String type;
private String description;

}
修改​​BookController​​ 类
@RestController
@RequestMapping("/books")
public class BookController {

@GetMapping
public String getById() {
System.out.println("getById is running... ");
return "springboot";
}

@GetMapping("{id}")
public Book getById(@PathVariable int id) {
System.out.println("getById is running... ");
Book book = new Book();
book.setId(1);
book.setName("springboot");
book.setType("springboot");
book.setDescription("springboot");
return book;
}

}
测试方法
// 注入虚拟mvc调用对象
@Test
void testJson(@Autowired MockMvc mvc) throws Exception {
// 创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
// 执行请求
ResultActions actions = mvc.perform(builder);
// 设定预期值 与真实值进行比较;成功测试通过,失败测试失败
// 定义本次调用的预期值
ContentResultMatchers content = MockMvcResultMatchers.content();
ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot\"}");
// 添加预计值到本次调用过程中进行匹配
actions.andExpect(result);
}
匹配失败的信息提示如:

开发实用篇(二)_spring_04

匹配响应头

虚拟请求响应头匹配

// 注入虚拟mvc调用对象
@Test
void testContentType(@Autowired MockMvc mvc) throws Exception {
// 创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
// 执行请求
ResultActions actions = mvc.perform(builder);
// 设定预期值 与真实值进行比较;成功测试通过,失败测试失败
// 定义本次调用的预期值
HeaderResultMatchers header = MockMvcResultMatchers.header();
ResultMatcher contentType = header.string("Content-Type", "application/json");
// 添加预计值到本次调用过程中进行匹配
actions.andExpect(contentType);
}
匹配失败的信息提示如:

开发实用篇(二)_数据库_05

基本上齐了,头信息,正文信息,状态信息都有了,就可以组合出一个完美的响应数据比对结果了。以下范例就是三种信息同时进行匹配校验,也是一个完整的信息匹配过程。
@Test
void testGetById(@Autowired MockMvc mvc) throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
ResultActions action = mvc.perform(builder);

StatusResultMatchers status = MockMvcResultMatchers.status();
ResultMatcher ok = status.isOk();
action.andExpect(ok);

HeaderResultMatchers header = MockMvcResultMatchers.header();
ResultMatcher contentType = header.string("Content-Type", "application/json");
action.andExpect(contentType);

ContentResultMatchers content = MockMvcResultMatchers.content();
ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\"}");
action.andExpect(result);
}
业务层测试事务回滚

为测试用例添加事务,SpringBoot 会对测试用例对应的事务提交操作进行回滚

@SpringBootTest
@Transactional
public class ServiceTest {

@Autowired
private BookService bookService;

@Test
void testSave() {
Book book = new Book();
book.setName("springboot5");
book.setType("springboot5");
book.setDescription("springboot5");
bookService.save(book);
}

}
如果想在测试用例中提交事务,可以通过​​@Rollback​​​ 注解设置回滚状态为​​false​​ 即可正常提交事务
@SpringBootTest
@Transactional
@Rollback(false)
public class ServiceTest {

@Autowired
private BookService bookService;

@Test
void testSave() {
Book book = new Book();
book.setName("springboot5");
book.setType("springboot5");
book.setDescription("springboot5");
bookService.save(book);
}

}

小结

在 springboot 的测试类中通过添加注解 @Transactional 来阻止测试用例提交事务通过注解 @Rollback 控制 springboot 测试类执行结果是否提交事务,需要配合注解@Transactional 使用测试用例设置随机数据

测试用例数据通常采用随机值进行测试,使用 SpringBoot 提供的随机数为其赋值

①: yml 中设置随机值

testcase:
book:
id: ${random.int}
id2: ${random.int(10)}
type: ${random.int!5,10!}
name: ${random.value}
uuid: ${random.uuid}
publishTime: ${random.long}

②: 创建​​BookCase​​ 类封装数据

// 1.定义数据模型封装yaml文件中对应的数据
// 2.定义为spring管控的bean
@Component
@Data
// 3.指定加载的数据
@ConfigurationProperties(prefix = "testcase.book")
public class BookCase {

private int id;
private int id2;
private int type;
private String name;
private String uuid;
private long publishTime;

}

③: 测试

@SpringBootTest
public class BookCaseRandom {

@Autowired
private BookCase bookCase;

@Test
void testProperties() {
System.out.println(bookCase);
}
}

对于随机值的产生,还有一些小的限定规则,比如产生的数值性数据可以设置范围等,具体如下:

开发实用篇(二)_spring boot_06

​${random.int}​​ 表示随机整数​​${random.int(10)}​​ 表示10以内的随机数​​${random.int(10,20)}​​ 表示10到20的随机数其中​​( )​​​可以是任意字符,例如​​[ ]、!!、@@​​ 均可

小结

使用随机数据替换测试用例中书写固定的数据数据层解决方案内置数据源 Hikari

现有数据层解决方案技术选型​​Mysql+Druid+MyBatisPlus​

数据源:DruidDataSource持久化技术:MyBatis-Plus / MyBatis数据库:MySQL

目前我们使用的数据源技术是Druid,运行时可以在日志中看到对应的数据源初始化信息,具体如下:

开发实用篇(二)_数据库_07

如果不配置数据源默认是​​HikariDataSource​

数据源配置格式

格式一 (通用配置未声明数据源技术)默认是​​hikari​​ 数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: 283619
格式二 (配置指定的数据源需要导入对应的 starter)
# druid 数据源配置
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: 283619
# hikari 数据源配置 url地址要单独配置 并且要去除druid数据源的依赖
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
hikari:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 283619

数据源配置

springboot 提供了3款内嵌​​数据源​​技术,分别如下:

HikariCP:默认内置数据源对象Tomcat提供DataSource:HikariCP不可用的情况下,且在web环境中,将使用tomcat服务器配置的数据源对象Commons DBCP:Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源

通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
username: root
password: 283619
hikari:
maximum-pool-size: 50
JdbcTemplate

内置​​持久化​​解决方案——JdbcTemplate

步骤①:导入jdbc对应的坐标,记得是starter

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

步骤②:自动装配JdbcTemplate对象

@SpringBootTest
class Springboot15SqlApplicationTests {
@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
}
}

步骤③:使用JdbcTemplate实现查询操作(非实体类封装数据的查询操作)

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "select * from tbl_book";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
System.out.println(maps);
}

步骤④:使用JdbcTemplate实现查询操作(实体类封装数据的查询操作)

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "select * from tbl_book";
RowMapper<Book> rm = new RowMapper<Book>() {
@Override
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
Book temp = new Book();
temp.setId(rs.getInt("id"));
temp.setName(rs.getString("name"));
temp.setType(rs.getString("type"));
temp.setDescription(rs.getString("description"));
return temp;
}
};
List<Book> list = jdbcTemplate.query(sql, rm);
System.out.println(list);
}

步骤⑤:使用JdbcTemplate实现增删改操作

@Test
void testJdbcTemplateSave(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "insert into tbl_book values(null,'springboot1','springboot2','springboot3')";
jdbcTemplate.update(sql);
}

如果想对 JdbcTemplate 对象进行相关配置,可以在yml文件中进行设定,具体如下:

spring:
jdbc:
template:
query-timeout: -1 # 查询超时时间
max-rows: 500 # 最大行数
fetch-size: -1 # 缓存行数

小结

SpringBoot内置JdbcTemplate持久化解决方案使用JdbcTemplate需要导入spring-boot-starter-jdbc的坐标H2数据库

SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率

H2HSQLDerby

内嵌数据库(H2)

步骤①:导入H2数据库对应的坐标,一共2个

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

步骤②:将工程设置为web工程,启动工程时启动H2数据库

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

步骤③:通过配置开启H2数据库控制台访问程序,也可以使用其他的数据库连接软件操作

# h2 数据库
server:
port: 80
spring:
h2:
console: # 控制台模式
enabled: true # 设置为true表示开启
path: /h2 # 访问路径

web端访问路径​​/h2​​​,访问密码​​123456​​​,如果访问失败,先配置下列数据源,启动程序运行后再次访问​​/h2​​路径就可以正常访问了

开发实用篇(二)_数据库_08

datasource:
url: jdbc:h2:~/test
hikari:
driver-class-name: org.h2.Driver
username: sa
password: 123456
操作数据库(创建表)
create table tbl_book (id int,name varchar,type varchar,description varchar)

开发实用篇(二)_java_09

步骤④:使用 JdbcTemplate 或 MyBatisPlus 技术操作数据库

记得测试的时候 要web服务器给关了,不然会报错

@SpringBootTest
class Springboot15SqlApplicationTests {

@Autowired
private BookDao bookDao;

@Test
void contextLoads() {
Book book = bookDao.selectById(1);
System.out.println(book);
}

/**
* 非实体类封装数据的查询操作
*
* @param jdbcTemplate
*/
@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "select * from tbl_book";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
System.out.println(maps);
}

/**
* 实体类封装数据的查询操作
*
* @param jdbcTemplate
*/
@Test
void testJdbcTemplate1(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "select * from tbl_book";
RowMapper<Book> rm = new RowMapper<Book>() {
@Override
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
Book temp = new Book();
temp.setId(rs.getInt("id"));
temp.setName(rs.getString("name"));
temp.setType(rs.getString("type"));
temp.setDescription(rs.getString("description"));
return temp;
}
};
List<Book> list = jdbcTemplate.query(sql, rm);
System.out.println(list);
}

@Test
void testJdbcTemplateSave(@Autowired JdbcTemplate jdbcTemplate) {
String sql = "insert into tbl_book values(3,'springboot1','springboot2','springboot3')";
jdbcTemplate.update(sql);
}
}
H2数据库控制台仅用于开发阶段,线上项目请务必关闭控制台功能

开发实用篇(二)_java_10

SpringBoot 可以根据url地址自动识别数据库种类,在保障驱动类存在的情况下,可以省略配置
# h2 数据库
server:
port: 80
spring:
h2:
console: # 控制台模式
enabled: true # 设置为true表示开启
path: /h2 # 访问路径
datasource:
url: jdbc:h2:~/test
hikari:
# driver-class-name: org.h2.Driver
username: sa
password: 123456

小结

H2内嵌式数据库启动方式,添加坐标,添加配置H2数据库线上运行时请务必关闭

开发实用篇(二)_java_11

总结

数据源配置(Hikari)持久化技术(JdbcTemplate)数据库(H2)


上一篇: 第五章 队列(Queue) 下一篇: 手机怎么远程登录云服务器?