添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

作为一名研发人员,不管你愿不愿意对自己的代码进行测试,都得承认测试对于研发质量保证的重要性,这也就是为什么每个公司的技术部都需要质量控制部的原因,因为越早的发现代码的bug,成本越低,比如说,Dev环境发现bug的成本要低于QA环境,QA环境发现bug的成本要低于Prod环境,Prod环境发现bug的成本最高,这也是每个研发人员最不愿意遇到但永远避不掉的现实。

虽然不能完全避免,但我们可以对自己的代码进行充分的测试,降低bug出现的几率。

所以, 本篇博客我们主要讲解下Spring MVC控制器的2种测试方法:

  • 部署项目后测试
  • 借助JUnit和Spring Test框架测试
  • 1. 部署项目后测试

    在前2篇博客中,我们采取的就是这种测试方式,即将项目打成war包,部署到Tomcat中,运行项目后, 借助浏览器或者Postman等工具对控制器进行测试。

    如果是get请求,可以使用浏览器或者Postman测试。

    如果是post、put、delete等请求,可以使用Postman进行测试。

    有兴趣的同学,可以看下之前的2篇博客:

    Spring入门(十二):Spring MVC使用讲解

    Spring入门(十三):Spring MVC常用注解讲解

    2. 借助Junit和Spring Test框架测试

    上面的方法虽然可以进行测试,但每次都打包、部署、运行项目、测试,显然很不方便,不过强大的Spring通过Spring Test框架对集成测试提供了支持,接下来我们讲解具体的使用方法。

    因为我们的Spring项目是通过maven管理的,所以它的项目结构有以下4个目录:

  • src/main/java:项目代码
  • src/main/resources:项目资源
  • src/test/java:测试代码
  • src/test/resources:测试资源(该目录默认没有生成,有需要的可以自己新建)
  • 也就是说,我们可以将我们的测试代码放在src/test/java目录下,不过截止目前,我们还并未在该目录添加任何测试代码。

    2.1 添加依赖

    在添加测试代码前,我们需要在pom.xml中添加如下依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.18.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    

    也许有的同学会好奇,为啥本次添加的依赖增加了<scope>test</scope>, 它有啥作用呢?

    带着这个疑问,我们编译下项目,发现原本编译正常的代码竟然编译报错了:

    报错信息提示程序包org.junit不存在,可我们明明添加了该依赖啊,这是为什么呢,会不会和<scope>test</scope>有关呢?

    恭喜你,猜对了,确实和<scope>test</scope>有关,如果你此时将该项移除,项目编译就不报错了(不过建议不要移除)。

    这是因为,我们在之前添加测试代码时,都是放在src/main/java目录下的,现在依赖包增加了<scope>test</scope>,说明这些包的存活周期是在test周期,所以我们可以把之前的测试代码移到src/test/java目录下,如下所示:

    再次编译项目,发现编译通过。

    2.2 添加控制器

    添加控制器前,新建DemoService如下所示:

    package chapter05.service;
    import org.springframework.stereotype.Service;
    @Service
    public class DemoService {
        public String saySomething() {
            return "hello";
    

    注意事项:该类添加了@Service注解。

    然后,新建控制器NormalController,它里面的方法返回jsp视图:

    package chapter05.controller;
    import chapter05.service.DemoService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    @Controller
    public class NormalController {
        @Autowired
        private DemoService demoService;
        @RequestMapping("/normal")
        public String testPage(Model model) {
            model.addAttribute("msg", demoService.saySomething());
            return "page";
    

    接着新建控制器MyRestController,它里面的方法直接返回信息:

    package chapter05.controller;
    import chapter05.service.DemoService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    public class MyRestController {
        @Autowired
        private DemoService demoService;
        @RequestMapping(value = "/testRest", produces = "text/plain;charset=UTF-8")
        public String testRest() {
            return demoService.saySomething();
    

    2.3 添加测试代码

    在src/test/java下新建包chapter05,然后在其下面新建测试类TestControllerIntegrationTests如下所示:

    package chapter05;
    import chapter05.config.MyMvcConfig;
    import chapter05.service.DemoService;
    import org.junit.Before;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = {MyMvcConfig.class})
    @WebAppConfiguration("src/main/resources")
    public class TestControllerIntegrationTests {
        private MockMvc mockMvc;
        @Autowired
        private DemoService demoService;
        @Autowired
        private WebApplicationContext webApplicationContext;
        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
    

    代码讲解:

    @RunWith(SpringJUnit4ClassRunner.class)用于在JUnit环境下提供Spring Test框架的功能。

    @ContextConfiguration(classes = {MyMvcConfig.class})用来加载配置ApplicationContext,其中classes属性用来加载配置类,MyMvcConfig配置类的代码如下所示:

    package chapter05.config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;
    import org.springframework.web.servlet.view.JstlView;
     * Spring MVC配置
    @Configuration
    @EnableWebMvc
    @ComponentScan("chapter05")
    public class MyMvcConfig {
         * 视图解析器配置
         * @return
        @Bean
        public InternalResourceViewResolver viewResolver() {
            InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
            viewResolver.setPrefix("/WEB-INF/classes/views/");
            viewResolver.setSuffix(".jsp");
            viewResolver.setViewClass(JstlView.class);
            return viewResolver;
    

    @WebAppConfiguration("src/main/resources") 用来声明加载的ApplicationContext是一个WebApplicationContext,它的属性指定的是Web资源的位置,默认为src/main/webapp,这里我们修改成

    src/main/resources。

    MockMvc用来模拟Mvc对象,它在添加了@Before注解的setup()中,通过this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();进行初始化赋值。

    然后往测试类中添加如下测试代码:

    @Test
    public void testNormalController() throws Exception {
        mockMvc.perform(get("/normal"))
                .andExpect(status().isOk())
                .andExpect(view().name("page"))
                .andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp"))
                .andExpect(model().attribute("msg", demoService.saySomething()));
    

    代码解释:

    perform(get("/normal"))用来模拟向/normal发起get请求,

    andExpect(status().isOk())预期返回的状态码为200,

    andExpect(view().name("page"))预期视图的逻辑名称为page,

    andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp"))预期视图的真正路径是/WEB-INF/classes/views/page.jsp",

    andExpect(model().attribute("msg", demoService.saySomething()))预期Model里有一个msg属性,它的值是demoService.saySomething()的返回值hello。

    执行该测试方法,测试通过:

    最后往测试类中添加如下测试代码:

    @Test
    public void testRestController() throws Exception {
        mockMvc.perform(get("/testRest"))
                .andExpect(status().isOk())
                .andExpect(content().contentType("text/plain;charset=UTF-8"))
                .andExpect(content().string(demoService.saySomething()));
    

    代码解释:

    perform(get("/testRest"))用来模拟向/testRest发起get请求,

    andExpect(status().isOk())预期返回的状态码为200,

    andExpect(content().contentType("text/plain;charset=UTF-8"))预期返回值的媒体类型为text/plain;charset=UTF-8,

    andExpect(content().string(demoService.saySomething()))预期返回值的内容为demoService.saySomething()的返回值hello。

    执行该测试方法,测试通过:

    3. 源码及参考

    源码地址:https://github.com/zwwhnly/spring-action.git,欢迎下载。

    Craig Walls 《Spring实战(第4版)》

    汪云飞《Java EE开发的颠覆者:Spring Boot实战》

    原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。

    如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。