本文最后更新于:2021年6月21日 中午
概略 SpringBoot 中进行测试比 Spring 项目中更加简单,想了解 Spring 项目中测试的可以参考 使用 Spring 时进行测试 。
普通测试 假设要测试一个工具类 StringUtil
(com.rxliuli.example.springboottest.util.StringUtil
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class StringUtil { public static boolean isEmpty (String string) { return string == null || string.isEmpty(); } public static boolean isNotEmpty (String string) { return !isEmpty(string); } public static boolean isAnyEmpty (String... strings) { return Arrays.stream(strings) .anyMatch(StringUtil::isEmpty); } public static boolean isAllEmpty (String... strings) { return Arrays.stream(strings) .allMatch(StringUtil::isEmpty); } }
需要添加依赖 spring-boot-starter-test
以及指定 assertj-core
的最新版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <dependencyManagement > <dependencies > <dependency > <groupId > org.assertj</groupId > <artifactId > assertj-core</artifactId > <version > 3.9.1</version > <scope > test</scope > </dependency > </dependencies > </dependencyManagement >
这里指定 assertj-core
的版本是为了使用较新的一部分断言功能(例如属性 lambda
断言)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class StringUtilTest { private String strNull = null ; private String strEmpty = "" ; private String strSome = "str" ; @Test public void isEmpty () { assertThat(StringUtil.isEmpty(strNull)) .isTrue(); assertThat(StringUtil.isEmpty(strEmpty)) .isTrue(); assertThat(StringUtil.isEmpty(strSome)) .isFalse(); } @Test public void isNotEmpty () { assertThat(StringUtil.isNotEmpty(strNull)) .isFalse(); assertThat(StringUtil.isNotEmpty(strEmpty)) .isFalse(); assertThat(StringUtil.isNotEmpty(strSome)) .isTrue(); } @Test public void isAnyEmpty () { assertThat(StringUtil.isAnyEmpty(strNull, strEmpty, strSome)) .isTrue(); assertThat(StringUtil.isAnyEmpty()) .isFalse(); } @Test public void isAllEmpty () { assertThat(StringUtil.isAllEmpty(strNull, strEmpty, strSome)) .isFalse(); assertThat(StringUtil.isAnyEmpty(strNull, strEmpty)) .isTrue(); } }
这里和非 SpringBoot 测试时没什么太大的区别,唯一的一点就是引入 Jar 不同,这里虽然我们只引入了 spring-boot-starter-test
,但它本身已经帮我们引入了许多的测试相关类库了。
Dao/Service 测试 从这里开始就和标准的 Spring 不太一样了
首先,我们需要 Dao 层,这里使用 H2DB 和 SpringJDBC 做数据访问层(比较简单)。
依赖
1 2 3 4 5 6 7 8 9 <dependency > <groupId > com.h2database</groupId > <artifactId > h2</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency >
添加两个初始化脚本
数据库结构 db_schema.sql
(db/db_schema.sql
)
1 2 3 4 5 6 7 8 9 10 11 drop table if exists user ;create table user ( id int auto_increment not null comment '编号' , name varchar (20 ) not null comment '名字' , sex boolean null comment '性别' , age int null comment '年龄' );
数据库数据 db_data.sql
(db/db_data.sql
)
1 2 3 4 insert into user (id, name, sex, age)values (1 , '琉璃' , false , 17 ), (2 , '月姬' , false , 1000 );
为 SpringBoot 配置一下数据源及初始化脚本
1 2 3 4 5 6 spring: datasource: driver-class-name: org.h2.Driver platform: h2 schema: classpath:db/db_schema.sql data: classpath:db/db_data.sql
然后是实体类与 Dao
用户实体类 User
(com.rxliuli.example.springboottest.entity.User
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class User implements Serializable { private Integer id; private String name; private Boolean sex; private Integer age; public User () { } public User (String name, Boolean sex, Integer age) { this .name = name; this .sex = sex; this .age = age; } public User (Integer id, String name, Boolean sex, Integer age) { this .id = id; this .name = name; this .sex = sex; this .age = age; } }
用户 Dao UserDao
(com.rxliuli.example.springboottest.dao.UserDao
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Repository public class UserDao { private final RowMapper<User> userRowMapper = (rs, rowNum) -> new User ( rs.getInt("id" ), rs.getString("name" ), rs.getBoolean("sex" ), rs.getInt("age" ) ); @Autowired private JdbcTemplate jdbcTemplate; public User get (Integer id) { return jdbcTemplate.queryForObject("select * from user where id = ?" , userRowMapper, id); } public List<User> listForAll () { return jdbcTemplate.query("select * from user" , userRowMapper); } public int deleteById (Integer id) { return jdbcTemplate.update("delete from user where id = ?" , id); } }
接下来才是正事,测试 Dao 层需要加载 Spring 容器,自动回滚以避免污染数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @SpringBootTest @RunWith(SpringRunner.class) @Transactional @Rollback public class UserDaoTest { @Autowired private UserDao userDao; @Test public void get () { int id = 1 ; User result = userDao.get(id); assertThat(result) .extracting(User::getId) .contains(id); } @Test public void listForAll () { List<User> userList = userDao.listForAll(); assertThat(userList) .isNotEmpty(); } @Test public void deleteById () { int result = userDao.deleteById(1 ); assertThat(result) .isGreaterThan(0 ); } }
Web 测试 与传统的 SpringTest 一样,SpringBoot 也分为两种。
独立安装测试: 手动加载单个 Controller,所以测试其他 Controller 中的接口会发生异常。但测试速度上较快,所以应当优先选择。
集成 Web 环境测试: 将启动并且加载所有的 Controller, 所以效率上之于 BaseWebUnitTest 来说非常低下, 仅适用于集成测试多个 Controller 时使用。
独立安装测试 主要是设置需要使用的 Controller 实例,然后用获得 MockMvc 对象进行测试即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @SpringBootTest @RunWith(SpringRunner.class) @Transactional @Rollback public class UserControllerUnitTest { @Autowired private UserController userController; private MockMvc mockMvc; @Before public void before () { mockMvc = MockMvcBuilders.standaloneSetup(userController) .build(); } @Test public void testGet () throws Exception { Integer id = 1 ; mockMvc.perform( get("/user/" + id) ) .andExpect(status().isOk()) .andExpect(jsonPath("$.id" ).value(id)); } @Test public void listForAll () throws Exception { mockMvc.perform( post("/user/listForAll" ) ) .andExpect(status().isOk()) .andExpect(jsonPath("$" ).isArray()) .andExpect(jsonPath("$" ).isNotEmpty()); } }
集成 Web 环境测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @SpringBootTest @RunWith(SpringRunner.class) @Transactional @Rollback public class UserControllerIntegratedTest { @Autowired private WebApplicationContext context; private MockMvc mockMvc; @Before public void before () { mockMvc = MockMvcBuilders.webAppContextSetup(context) .build(); } @Test public void testGet () throws Exception { Integer id = 1 ; mockMvc.perform( get("/user/" + id) ) .andExpect(status().isOk()) .andExpect(jsonPath("$.id" ).value(id)); } @Test public void listForAll () throws Exception { mockMvc.perform( post("/user/listForAll" ) ) .andExpect(status().isOk()) .andExpect(jsonPath("$" ).isArray()) .andExpect(jsonPath("$" ).isNotEmpty()); } }
总结 其实上面的测试类的注解感觉都差不多,我们可以将一些普遍的注解封装到基类,然后测试类只要继承基类就能得到所需要的环境,吾辈自己的测试基类在 src/test/common
下面,具体使用方法便留到下次再说吧
以上代码已全部放到 GitHub 上面,可以直接 clone 下来进行测试