Springboot单元测试

摘要:
可以使用Spring自己的弹簧引导测试工具类。spring-bootstarter测试启动器可以引入这些SpringBoot测试模块:JUnit:Java应用程序单元测试标准类库。如果要创建spring容器,必须在web.xml中配置classloader。使用@RunWith注释,可以直接使用spring容器,也可以直接使用@Test注释。您不需要启动弹簧容器。3.控制器单元测试仅针对服务层,但有时需要测试控制器层。此时,您需要使用MockMvc。您可以在不启动项目的情况下测试这些接口。

一、前言

这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求

  • Service层单元测试
  • Controller层单元测试
  • 新断言assertThat使用
  • 单元测试的回滚

Spring Boot中引入单元测试很简单,依赖如下:

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

在生成的测试类中就可以写单元测试了。用spring自带spring-boot-test的测试工具类即可,spring-boot-starter-test 启动器能引入这些 Spring Boot 测试模块:

  • JUnit:Java 应用程序单元测试标准类库。
  • Spring Test & Spring Boot Test:Spring Boot 应用程序功能集成化测试支持。
  • Mockito:一个Java Mock测试框架。
  • AssertJ:一个轻量级的断言类库。
  • Hamcrest:一个对象匹配器类库。
  • JSONassert:一个用于JSON的断言库。
  • JsonPath:一个JSON操作类库。

二、Service单元测试

Spring Boot中单元测试类写在在src/test/java目录下,你可以手动创建具体测试类,如果是IDEA,则可以通过IDEA自动创建测试类,如下图,也可以通过快捷键Ctrl+Shift+T(Window)来创建,如下:

Springboot单元测试第1张

然后再编写创建好的测试类:

package com.dudu.service;
import com.dudu.domain.LearnResource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.CoreMatchers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {

    @Autowired
    private LearnService learnService;
    
    @Test
    public void getLearn(){
        LearnResource learnResource=learnService.selectByKey(1001L);
        Assert.assertThat(learnResource.getAuthor(),is("嘟嘟MD独立博客"));
    }
}

上面就是最简单的单元测试写法,顶部只要@RunWith(SpringRunner.class)SpringBootTest即可,想要执行的时候,鼠标放在对应的方法,右键选择run该方法即可。

RunWith解释

@RunWith就是一个运行器,如 @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,@RunWith(SpringJUnit4ClassRunner.class)使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。其他的想创建spring容器的话,就得子啊web.xml配置classloder。 注解了@RunWith就可以直接使用spring容器,直接使用@Test注解,不用启动spring容器

三、Controller单元测试

上面只是针对Service层做测试,但是有时候需要对Controller层(API)做测试,这时候就得用到MockMvc了,你可以不必启动工程就能测试这些接口。

MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。

Controller类:

package com.dudu.controller;

/** 教程页面
 * Created by tengj on 2017/3/13.
 */
@Controller
@RequestMapping("/learn")
public class LearnController  extends AbstractController{
    @Autowired
    private LearnService learnService;
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("")
    public String learn(Model model){
        model.addAttribute("ctx", getContextPath()+"/");
        return "learn-resource";
    }

    /**
     * 查询教程列表
     * @param page
     * @return
     */
    @RequestMapping(value = "/queryLeanList",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject queryLearnList(Page<LeanQueryLeanListReq> page){
        List<LearnResource> learnList=learnService.queryLearnResouceList(page);
        PageInfo<LearnResource> pageInfo =new PageInfo<LearnResource>(learnList);
        return AjaxObject.ok().put("page", pageInfo);
    }
    
    /**
     * 新添教程
     * @param learn
     */
    @RequestMapping(value = "/add",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject addLearn(@RequestBody LearnResource learn){
        learnService.save(learn);
        return AjaxObject.ok();
    }

    /**
     * 修改教程
     * @param learn
     */
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject updateLearn(@RequestBody LearnResource learn){
        learnService.updateNotNull(learn);
        return AjaxObject.ok();
    }

    /**
     * 删除教程
     * @param ids
     */
    @RequestMapping(value="/delete",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject deleteLearn(@RequestBody Long[] ids){
        learnService.deleteBatch(ids);
        return AjaxObject.ok();
    }

    /**
     * 获取教程
     * @param id
     */
    @RequestMapping(value="/resource/{id}",method = RequestMethod.GET)
    @ResponseBody
    public LearnResource qryLearn(@PathVariable(value = "id") Long id){
       LearnResource lean= learnService.selectByKey(id);
        return lean;
    }
}

这里我们也自动创建一个Controller的测试类,具体代码如下:

package com.dudu.controller;

import com.dudu.domain.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest

public class LearnControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mvc;
    private MockHttpSession session;


    @Before
    public void setupMockMvc(){
        mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
        session = new MockHttpSession();
        User user =new User("root","root");
        session.setAttribute("user",user); //拦截器那边会判断用户是否登录,所以这里注入一个用户
    }

    /**
     * 新增教程测试用例
     * @throws Exception
     */
    @Test
    public void addLearn() throws Exception{
        String json="{"author":"HAHAHAA","title":"Spring","url":"http://tengj.top/"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/add")
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content(json.getBytes()) //传json参数
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 获取教程测试用例
     * @throws Exception
     */
    @Test
    public void qryLearn() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
           .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
           .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 修改教程测试用例
     * @throws Exception
     */
    @Test
    public void updateLearn() throws Exception{
        String json="{"author":"测试修改","id":1031,"title":"Spring Boot干货系列","url":"http://tengj.top/"}";
        mvc.perform(MockMvcRequestBuilders.post("/learn/update")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//传json参数
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    /**
     * 删除教程测试用例
     * @throws Exception
     */
    @Test
    public void deleteLearn() throws Exception{
        String json="[1031]";
        mvc.perform(MockMvcRequestBuilders.post("/learn/delete")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .content(json.getBytes())//传json参数
                .session(session)
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

}

上面实现了基本的增删改查的测试用例,使用MockMvc的时候需要先用MockMvcBuilders使用构建MockMvc对象,如下

@Before
public void setupMockMvc(){
    mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
    session = new MockHttpSession();
    User user =new User("root","root");
    session.setAttribute("user",user); //拦截器那边会判断用户是否登录,所以这里注入一个用户
}

因为拦截器那边会判断是否登录,所以这里我注入了一个用户,你也可以直接修改拦截器取消验证用户登录,先测试完再开启。

这里拿一个例子来介绍一下MockMvc简单的方法

/**
 * 获取教程测试用例
 * @throws Exception
 */
@Test
public void qryLearn() throws Exception {
    mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
       .andExpect(MockMvcResultMatchers.status().isOk())
       .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
       .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
       .andDo(MockMvcResultHandlers.print());
}
  1. mockMvc.perform执行一个请求
  2. MockMvcRequestBuilders.get(“/user/1”)构造一个请求,Post请求就用.post方法
  3. contentType(MediaType.APPLICATION_JSON_UTF8)代表发送端发送的数据格式是application/json;charset=UTF-8
  4. accept(MediaType.APPLICATION_JSON_UTF8)代表客户端希望接受的数据类型为application/json;charset=UTF-8
  5. session(session)注入一个session,这样拦截器才可以通过
  6. ResultActions.andExpect添加执行完成后的断言
  7. ResultActions.andExpect(MockMvcResultMatchers.status().isOk())方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过
  8. andExpect(MockMvcResultMatchers.jsonPath(“$.author”).value(“嘟嘟MD独立博客”))这里jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
  9. ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息

四、新断言assertThat使用

JUnit 4.4 结合 Hamcrest 提供了一个全新的断言语法——assertThat。程序员可以只使用 assertThat 一个断言语句,结合 Hamcrest 提供的匹配符,就可以表达全部的测试思想,我们引入的版本是Junit4.12所以支持assertThat。

清单 1 assertThat 基本语法

assertThat( [value], [matcher statement] );  
  • value 是接下来想要测试的变量值;
  • matcher statement 是使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明,如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。

assertThat 的优点

  • 优点 1:以前 JUnit 提供了很多的 assertion 语句,如:assertEquals,assertNotSame,assertFalse,assertTrue,assertNotNull,assertNull 等,现在有了 JUnit 4.4,一条 assertThat 即可以替代所有的 assertion 语句,这样可以在所有的单元测试中只使用一个断言方法,使得编写测试用例变得简单,代码风格变得统一,测试代码也更容易维护。
  • 优点 2:assertThat 使用了 Hamcrest 的 Matcher 匹配符,用户可以使用匹配符规定的匹配准则精确的指定一些想设定满足的条件,具有很强的易读性,而且使用起来更加灵活。如清单 2 所示

清单 2 使用匹配符 Matcher 和不使用之间的比较

// 想判断某个字符串 s 是否含有子字符串 "developer" 或 "Works" 中间的一个
// JUnit 4.4 以前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
// JUnit 4.4:
assertThat(s, anyOf(containsString("developer"), containsString("Works"))); 
// 匹配符 anyOf 表示任何一个条件满足则成立,类似于逻辑或 "||", 匹配符 containsString 表示是否含有参数子 
// 字符串,文章接下来会对匹配符进行具体介绍

优点 3:assertThat 不再像 assertEquals 那样,使用比较难懂的“谓宾主”语法模式(如:assertEquals(3, x);),相反,assertThat 使用了类似于“主谓宾”的易读语法模式(如:assertThat(x,is(3));),使得代码更加直观、易读。

免责声明:文章转载自《Springboot单元测试》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇神经网络+增强学习[信安Presentation]一种基于GPU并行计算的MD5密码解密方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Error invoking SqlProvider method (com.github.abel533.mapper.MapperProvider.dynamicSQL). Cause: java.lang.InstantiationException: com.github.abel533.mapper.MapperProvider

org.apache.ibatis.exceptions.PersistenceException: ### Error querying database.  Cause: org.apache.ibatis.builder.BuilderException: Error invoking SqlProvider method (com.github.a...

SSM 三大框架系列:Spring 5 + Spring MVC 5 + MyBatis 3.5 整合(附源码)

之前整理了一下新版本的 SSM 三大框架,这篇文章是关于它的整合过程和项目源码,版本号分别为:Spring 5.2.2.RELEASE、SpringMVC 5.2.2.RELEASE、MyBatis 3.5.2。 背景介绍 ssm-demo 是我发布到 GitHub 上的第一个开源项目,该项目开发时应该是 2016 年底的时候,之后是选择将这个项目开源到...

整理分布式锁:业务场景&amp;amp;分布式锁家族&amp;amp;实现原理

1、引入业务场景 业务场景一出现: 因为小T刚接手项目,正在吭哧吭哧对熟悉着代码、部署架构。在看代码过程中发现,下单这块代码可能会出现问题,这可是分布式部署的,如果多个用户同时购买同一个商品,就可能导致商品出现库存超卖 (数据不一致)现象,对于这种情况代码中并没有做任何控制。 原来一问才知道,以前他们都是售卖的虚拟商品,没啥库存一说,所以当时没有考虑那么多...

SpringBoot入门 (三) 日志配置

上一篇博文记录了再springboot项目中读取属性文件中配置的属性,本文学习在springboot项目中记录日志。   日志记录在项目中是很常见的一个功能了,对排查问题有很大帮助,也可以做分类分析及统计。SpringBoot内部使用的是Commons Logging做日志的记录,但是对其他的日志框架也提供了默认的配置,如:Java util Loggin...

SSH框架总结

首先,SSH不是一个框架,而是多个框架(struts+spring+hibernate)的集成,是目前较流行的一种Web应用程序开源集成框架,用于构建灵活、易于扩展的多层Web应用程序。 集成SSH框架的系统从职责上分为四层:表示层、业务逻辑层、数据持久层和域模块层(实体层)。 Struts作为系统的整体基础架构,负责MVC的分离,在Struts框架的模...

Spring源码阅读-IoC容器解析

目录 Spring IoC容器 ApplicationContext设计解析 BeanFactory ListableBeanFactory HierarchicalBeanFactory MessageSource ApplicationEventPublisher EnvironmentCapable ResourceLoader和Resour...