这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下3点来介绍,基本满足日常需求:
Dao层单元测试
Service层单元测试
Controller层单元测试 在单元测试中要尽量使用断言,本文所有的测试类都符合几个原则:
测试类卸载src/test/java目录下
测试类的包结构与被测试类的包结构相同
测试类的命名都是被测试类类名后缀加上Test,例如,UserDaoImpl与UserDaoImplTest相对应
测试类的方法与被测试类的方法命名相同
1.前言 这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下3点来介绍,基本满足日常需求:
Dao层单元测试
Service层单元测试
Controller层单元测试 在单元测试中要尽量使用断言,本文所有的测试类都符合几个原则:
测试类卸载src/test/java目录下
测试类的包结构与被测试类的包结构相同
测试类的命名都是被测试类类名后缀加上Test,例如,UserDaoImpl与UserDaoImplTest相对应
测试类的方法与被测试类的方法命名相同
2.正文 2.1核心依赖 在Spring Boot 项目中引入单元测试很简单,依赖如下:
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency >
2.2 如何创建单元测试类 Spring Boot中单元测试类写在在src/test/java目录下,你可以手动创建具体测试类,如果是IDEA,则可以通过IDEA自动创建测试类,如下图,可以通过快捷键Ctrl+Shift+T(Window)来创建,如下:
2.3. 盲点解释 @Runwith JUnit用例都是在Runner(运行器)来执行的。通过它,可以为这个测试类指定一个特定的Runner。 JUnit允许用户指定其它的单元测试执行类,只需要我们的测试执行类继承类org.junit.runners.BlockJUnit4ClassRunner就可以了,Spring的执行类SpringJUnit4ClassRunner就是继承了该类。我们平时用Spring也比较多,为了能够更加方便的引用配置文件,我们单元测试就使用了Spring实现的执行类。此时的单元测试执行类将会看起来是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RunWith(SpringRunner.class) @SpringBootTest public class UserDaoImplTest { @Autowired private UserDao dao; @Test public void getByUsername () { User user = dao.getByUsername("小明" ); System.err.println(user); }
SpringRunner SpringRunner
是SpringJUnit4ClassRunner
的一个别名。SpringJUnit4ClassRunner
是JUnit的BlockJUnit4ClassRunner
类的一个常规扩展,提供了一些spring测试环境上下文去规范JUnit测试。
@SpringBootTest 注解制定了一个测试类运行了Spring Boot环境。提供以下特性:
自动搜索到SpringBootConfiguration注解的文件。
允许自动注入Environment类读取配置文件。
提供一个webEnvironment环境,可以完整的允许一个web环境使用随机的端口或者自定义的端口。
Spring Boot测试步骤 直接在测试类上面加上如下2个注解@RunWith(SpringRunner.class)
@SpringBootTest
就能取到spring中的容器的实例,如果配置了@Autowired那么就自动将对象注入。
单元测试回滚 单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,记在方法或者类头部添加@Transactional注解即可,如下:
1 2 3 4 5 6 7 @Transactional @Test public void save () { User user = new User("测试用户" , "123456" ); int save = dao.save(user); Assert.assertEquals(1 , save); }
这样测试完数据就会回滚了,不会造成垃圾数据。
3.核心代码示例 3.1.Dao层单元测试 基本上所有的WEB程序都会涉及到数据库,本次示例就以最简化的模式: 持久层框架就用spring-jdbc。 Dao层的测试涉及到基本的CRUD操作。
UserDaoImpl 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 52 53 54 55 56 57 58 59 60 61 62 @Repository public class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate template; private static final BeanPropertyRowMapper<User> MAPPER = new BeanPropertyRowMapper<>(User.class); @Override public int save (User user) { return template.update( "insert into user(username, password) values (?, ?)" , user.getUsername(), user.getPassword()); } @Override public int update (User user) { return template.update( "update user set password = ? where username = ?" , user.getPassword(), user.getUsername()); } @Override public int delete (Long id) { return template.update("delete from user where id = ?" , id); } @Override public List<User> list () { return template.query("select id, username, password from user" , MAPPER); } @Override public User getByUsername (String username) { try { return template.queryForObject( "select id, username, password from user where username = ? limit 1" , MAPPER, username); } catch (EmptyResultDataAccessException e) { return null ; } } }
UserDaoImplTest 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @RunWith(SpringRunner.class) @SpringBootTest public class UserDaoImplTest { @Autowired private UserDao dao; @Test public void getByUsername () { User user = dao.getByUsername("小明" ); System.err.println(user); } @Transactional @Test public void save () { User user = new User("测试用户" , "123456" ); int save = dao.save(user); Assert.assertEquals(1 , save); } @Transactional @Test public void update () { User user = new User("测试用户" , "123456" ); dao.save(user); User before = dao.getByUsername("测试用户" ); user.setPassword("654321" ); dao.update(user); User after = dao.getByUsername("测试用户" ); Assert.assertNotEquals(before, after); } @Transactional @Test public void delete () { int save = dao.save(new User("测试用户" , "123456" )); Assert.assertEquals(1 , save); User user = dao.getByUsername("测试用户" ); int delete = dao.delete(user.getId()); Assert.assertEquals(1 , delete); } @Test public void list () { List<User> list = dao.list(); System.err.println(list); } }
3.2.Service层单元测试 UserServiceImpl 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 52 53 54 55 56 57 58 @Service public class UserServiceImpl implements UserService { @Autowired private UserDao dao; @Override public User getByUsername (String username) { return dao.getByUsername(username); } @Override public List<User> list () { return dao.list(); } @Transactional @Override public int updatePassword (User user) { return dao.update(user); } @Transactional @Override public int deleteById (Long id) { return dao.delete(id); } @Transactional @Override public int save (User user) { return dao.save(user); } }
UserServiceImplTest 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @RunWith(SpringRunner.class) @SpringBootTest public class UserServiceImplTest { @Autowired private UserService service; @Test public void getByUsername () { User user = service.getByUsername("小明" ); System.err.println(user); } @Test public void list () { List<User> list = service.list(); System.err.println(list); } @Transactional @Test public void updatePassword () { User user = new User("测试用户" , "123456" ); service.save(user); User before = service.getByUsername("测试用户" ); user.setPassword("654321" ); service.updatePassword(user); User after = service.getByUsername("测试用户" ); Assert.assertNotEquals(before, after); } @Transactional @Test public void deleteById () { int save = service.save(new User("测试用户" , "123456" )); Assert.assertEquals(1 , save); User user = service.getByUsername("测试用户" ); int delete = service.deleteById(user.getId()); Assert.assertEquals(1 , delete); } @Transactional @Test public void save () { List<User> list = service.list(); System.err.println(list); } }
3.3.Controller层单元测试 上面只是针对Service和Dao层做测试,但是有时候需要对Controller层(API)做测试,这时候就得用到MockMvc了,你可以不必启动工程就能测试这些接口。
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。
UserController 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 52 53 54 55 56 57 58 @RestController public class UserController { @Autowired private UserService service; @RequestMapping("/user/list") public List<User> list () { return service.list(); } @RequestMapping("/user/save") public int save (@RequestParam String username, @RequestParam String password) { return service.save(new User(username, password)); } @RequestMapping("/user/delete") public int delete (@RequestParam Long id) { return service.deleteById(id); } @RequestMapping("/user/getByUsername") public User getByUsername (@RequestParam String username) { return service.getByUsername(username); } @RequestMapping("/user/updatepassword") public int updatepassword (@RequestParam String username, @RequestParam String newPassword) { return service.updatePassword(new User(username, newPassword)); } }
UserControllerTest 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class UserControllerTest { MockMvc mvc; @Autowired WebApplicationContext context; @Before public void before () { mvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Test public void list () throws Exception { mvc.perform( MockMvcRequestBuilders.get("/user/list" )) .andExpect(status().isOk()) .andDo(print()); } @Transactional @Test public void save () throws Exception { mvc.perform( MockMvcRequestBuilders.post("/user/save" ) .param("username" , "测试用户1" ) .param("password" , "123456" )) .andExpect(status().isOk()) .andDo(print()); } @Transactional @Test public void delete () throws Exception { mvc.perform(MockMvcRequestBuilders.post("/user/delete" ) .param("id" , "1" )) .andExpect(status().isOk()) .andDo(print()); } @Test public void getByUsername () throws Exception { mvc.perform(MockMvcRequestBuilders.get("/user/getByUsername" ) .param("username" , "小明" )) .andExpect(status().isOk()) .andDo(print()); } @Transactional @Test public void updatepassword () throws Exception { mvc.perform(MockMvcRequestBuilders.post("/user/updatepassword" ) .param("username" , "小明" ) .param("newPassword" , "654321" )) .andExpect(status().isOk()) .andDo(print()); } }