SpringBoot 1.4 之后测试代码该如何写

作者: HikariCP | 来源:发表于2017-11-06 02:19 被阅读710次

    前言

    测试在 SpringBoot 1.4 版本之后进行了改进,Testing improvements in Spring Boot 1.4。由于 Spring Boot 1.4 之前的测试方式均属于集成测试。一个单纯的单元测试不应该创建和加载 Spring 的上下文。在 1.4 版本之前想要使用单元测试测试一个带有@Autowired注解的外部 services 的 controller 而不用去加载Spring上下文,是不可能的。


    简介

    Spring Boot 1.4 解决的另外一个问题是,可以测试一段代码。不用启动服务器。并且不用启动整个Spring上下文,Spring Boot 1.4 通过新的Test Slicing的特性 就可以完成,这个特性被设计成可以至启动一小片的Spring上下文。这时的测试单个的代码片段更加容易了。你可以这样去测试你的应用中的特定代码片段

    • MVC 片段: 通过@WebMvcTest注解测试Controller代码
    • JPA 片段: 通过@DataJpaTest注解测试Spring Data JPA repository代码
    • JSON 片段: 通过@JsonTest注解JSON序列化代码

    解决的问题;大型的应用要测试的话,如果它启动Spring上下文,会很耗费时间


    SpringBoot 1.4-

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    public class ApplicationTests {
    
    }
    

    Or

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = Application.class)
    public class ApplicationTests {
    
    }
    

    SpringBoot 1.4+

    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
    public class ApplicationTests {
    
    }
    

    注解

    • @RunWith(SpringRunner.class) 告诉Spring运行使用的JUnit测试支持。SpringRunnerSpringJUnit4ClassRunner的新名字,这个名字只是让名字看起来简单些。
    • @SpringBootTest意思是“带有Spring Boot支持的引导程序”(例如,加载应用程序、属性,为我们提供Spring Boot的所有精华部分)。
      • webEnvironment属性允许为测试配置特定的“网络环境”。
        • MOCK:提供一个Mock的Servlet环境,内置的Servlet容器并没有真实的启动,主要搭配使用@AutoConfigureMockMvc
        • RANDOM_PORT: 提供一个真实的Servlet环境,也就是说会启动内置容器,然后使用的是随机端口
        • DEFINED_PORT:这个配置也是提供一个真实的Servlet环境,使用的默认的端口,如果没有配置就是8080
        • NONE:这是个神奇的配置,跟Mock一样也不提供真实的Servlet环境。
      • classes如果想要加载一个特定的配置,可以用@SpringBootTest的classes属性。在这个实例中,省略classes就意味着测试要首次尝试从任意一个inner-classes中加载@configuration,如果这个尝试失败了,它会在你主要的@SpringBootApplicationclass中进行搜索。

    实例

    单例测试

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = HelloController.class)
    @AutoConfigureWebMvc
    public class HelloControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
        
        @Test
        public void index() throws Exception {
            this.mockMvc.perform(get("/hello"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("application/json;charset=UTF-8"))
    //                .andExpect(view().name("index"))
                    .andExpect(content().string(Matchers.containsString("Hello World")))
                    .andDo(print());
        }
    }
    
    • @SpringBootTest注解不同@WebMvcTest注解会把自动配置给禁用掉。
      • @WebMvcTest只会将 Spring MVC 的基础架构自动配置,并且仅对使用@Controller,@ControllerAdvice,@JsonComponent注解的bean ,以及FilterWebMvcConfigurerHandlerMethodArgumentResolver类型的bean进行扫描。此时@Component、@Service 或 @Repository注解的bean将不会被扫描到

    集成测试

    @RunWith(SpringRunner.class)
    //@WebMvcTest(controllers = HelloController.class)
    //@AutoConfigureWebMvc
    public class HelloControllerTest extends C1ApplicationTests {
    
    //    @Autowired
        private MockMvc mockMvc;
    
        @Before
        public void setUp() throws Exception {
            mockMvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
        }
    
        @Test
        public void index() throws Exception {
            this.mockMvc.perform(get("/hello"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentType("application/json;charset=UTF-8"))
    //                .andExpect(view().name("index"))
                    .andExpect(content().string(Matchers.containsString("Hello World")))
                    .andDo(print());
        }
    }
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = C1Application.class)
    public class C1ApplicationTests {
        
    }
    
    • 如果想要加载所有的应用配置并且使用 MockMVC,就应该使用@SpringBootTest注解并且加上 @AutoConfigureMockMvc注解,而不是使用@WebMvcTest注解。
    • MockMvc 通过模拟 Spring MVC 来测试 MVC 网页应用。可以向一个controller发送模拟的HTTP请求,这样不再需要启动应用服务器。可以通过 MockMvcBuilders 来获取 MockMvc 的实例。
      • (推荐)standaloneSetup():注册一个或多个@Controller实例,并且允许通过编程去配置 Spring MVC 的基础架构 从而来构造一个 MockMvc 的实例。 这跟普通的单元测试很相似,同时也使得一次仅关注一个controller的测试成为可能。
      • webAppContextSetup(): 使用完全被初始化(并且刷新过)了的 WebApplicationContext 来构建一个MockMvc实例。这样使Spring可以加载你的控制层以及它们的所有依赖,从而进行一个完整的集成测试。

    方法解析:

    • perform:执行一个RequestBuilder请求,会自动执行 SpringMVC 的流程并映射到相应的控制器执行处理;
    • get:声明发送一个get请求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个 GET 请求方式的。另外提供了其他的请求的方法,如:post、put、delete等。
    • param:添加request的参数,假如使用需要发送json数据格式的时将不能使用这种方式,可见后面被@ResponseBody注解参数的解决方法
    • andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断);
    • andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断);
    • andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断);

    控制台

    MockHttpServletRequest:
          HTTP Method = GET
          Request URI = /hello
           Parameters = {}
              Headers = {}
    
    Handler:
                 Type = nuc.jyg.c1.web.HelloController
               Method = public java.lang.String nuc.jyg.c1.web.HelloController.index()
    
    Async:
        Async started = false
         Async result = null
    
    Resolved Exception:
                 Type = null
    
    ModelAndView:
            View name = null
                 View = null
                Model = null
    
    FlashMap:
           Attributes = null
    
    MockHttpServletResponse:
               Status = 200
        Error message = null
              Headers = {Content-Type=[application/json;charset=UTF-8], Content-Length=[11]}
         Content type = application/json;charset=UTF-8
                 Body = Hello World
        Forwarded URL = null
       Redirected URL = null
              Cookies = []
    

    扩展

    事物回滚

    @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
    @Transactional
    

    上面两句的作用是,让我们对数据库的操作会事务回滚,如对数据库的添加操作,在方法结束之后,会撤销我们对数据库的操作。

    为什么要事务回滚?

    • 测试过程对数据库的操作,会产生脏数据,影响数据的正确性
    • 不方便循环测试,即假如这次将一个记录删除了,下次就无法再进行这个Junit测试了,因为该记录已经删除,将会报错。
    • 如果不使用事务回滚,需要在代码中显式的对增删改数据库操作进行恢复,将多很多和测试无关的代码

    这里需要注意,如果使用了事物回滚。那么有些时候对数据库内容进行修改操作后你将不能直观的看到变化。如何判断是否成功了,可以在返回的数据中带上data字段,将修改的数据存进去传向前台。

    使用andExpect方法对返回的数据进行判断,用“$.属性”获取里面的数据,如要获取返回数据中的"data.name",可以写成"$.data.name"。下面的例子是判断返回的 data.name = “测试”。

    MockHttpServletRequestBuilder.andExpect(jsonPath("$.data.name", is("##")))) 
    

    不同环境的测试

    @ActiveProfiles(profiles = "test") 在测试类上面指定profiles,可以改变当前spring 的profile,来达到多环境的测试

    相关文章

      网友评论

        本文标题:SpringBoot 1.4 之后测试代码该如何写

        本文链接:https://www.haomeiwen.com/subject/eijkmxtx.html