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

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

本篇实例Spring Boot版本为1.5.9.RELEASE,引入spring-boot-starter-test后,有如下几个库:
- JUnit — The de-facto standard for unit testing Java applications.
- Spring Test & Spring Boot Test — Utilities and integration test support for Spring Boot applications.
- AssertJ — A fluent assertion library.
- Hamcrest — A library of matcher objects (also known as constraints or predicates).
- Mockito — A Java mocking framework.
- JSONassert — An assertion library for JSON.
- JsonPath — XPath for JSON.

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

菜单 -> Navigate -> Test -> 勾选Member -> OK

Service单元测试

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 UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void getUser(){
        User user=userService.selectByUserName("admin");
        Assert.assertThat(user.getUserName(),is("竹智熊猫博客"));
    }
}

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

测试用例中我使用了assertThat断言,下文中会介绍,也推荐大家使用该断言。

Controller单元测试

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

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

/** 教程页面
 * Created by tengj on 2017/3/13.
 */
@Controller
public class UserController  extends AbstractController{
    @Autowired
    private UserService userService;
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 查询教程列表
     * @param page
     * @return
     */
    @RequestMapping(value = "/queryList",method = RequestMethod.POST)
    @ResponseBody
    public AjaxObject queryUserList(Page<LeanQueryLeanListReq> page){
        List<UserResource> userList=userService.queryUserResouceList(page);
        PageInfo<UserResource> pageInfo =new PageInfo<UserResource>(userList);
        return AjaxObject.ok().put("page", pageInfo);
    }
}

这里我们也自动创建一个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 UserControllerTest {
    @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 addUser() throws Exception{
        String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
        mvc.perform(MockMvcRequestBuilders.post("/queryList")
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content(json.getBytes()) //传json参数
                    .session(session)
            )
           .andExpect(MockMvcResultMatchers.status().isOk())
           .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()输出整个响应结果信息

单元测试回滚

单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,记在方法或者类头部添加@Transactional注解即可,如下:

@Test
@Transactional
public void add(){
    LearnResource bean = new LearnResource();
    bean.setAuthor("测试回滚");
    bean.setTitle("回滚用例");
    bean.setUrl("http://tengj.top");
    learnService.save(bean);
}

这样测试完数据就会回滚了,不会造成垃圾数据。如果你想关闭回滚,只要加上@Rollback(false)注解即可。@Rollback表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。

如果你使用的数据库是Mysql,有时候会发现加了注解@Transactional 也不会回滚,那么你就要查看一下你的默认引擎是不是InnoDB,如果不是就要改成InnoDB。

MyISAM与InnoDB是mysql目前比较常用的两个数据库存储引擎,MyISAM与InnoDB的主要的不同点在于性能和事务控制上。这里简单的介绍一下两者间的区别和转换方法:

  • MyISAM:MyISAM是MySQL5.5之前版本默认的数据库存储引擎。MYISAM提供高速存储和检索,以及全文搜索能力,适合数据仓库等查询频繁的应用。但不支持事务、也不支持外键。MyISAM格式的一个重要缺陷就是不能在表损坏后恢复数据。
  • InnoDB:InnoDB是MySQL5.5版本的默认数据库存储引擎,不过InnoDB已被Oracle收购,MySQL自行开发的新存储引擎Falcon将在MySQL6.0版本引进。InnoDB具有提交、回滚和崩溃恢复能力的事务安全。但是比起MyISAM存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。尽管如此,但是InnoDB包括了对事务处理和外来键的支持,这两点都是MyISAM引擎所没有的。
  • MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。
  • InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且表锁定的机会比较大的情况。(4)性能较好的服务器,比如单独的数据库服务器,像阿里云的关系型数据库RDS就推荐使用InnoDB引擎。

修改mysql默认引擎

参考

嘟嘟独立博客