如何对Springboot项目进行单元测试

1 为什么需要单元测试

在平时的开发当中,一个项目往往包含了大量的方法,可能有成千上万个。如何去保证这些方法产生的结果是我们想要的呢?

传统解决方案:Postman 发报文,System.out打印debug日志,或者眼睛看返回报文

1、眼睛看结果是否正确,瞅瞎不说,也太不智能.我们是高智商程序员,能让代码解决的事情,绝不能靠人工去解决.

2、postman 只能对controller进行测试。controller要正确,前提是service,dao都正确。发现问题太晚,解决成本高。

3、对于一些交易系统,由于交易主键的存在,每次都要更改参数后,再进行测试,效率太低。

4、无法对内部的函数功能做测试

5、postman的测试案例与项目工程不再一起,这些案例只能自己一个人用,无法团队共享

这时,就轮到单元测试闪亮出场了。

  • 测试代码和工程代码在同一工程文件中,便于维护和传承。

  • 使用断言自动检测结果

  • 测试粒度小,可以小到每个函数

  • 测试模块间相互依赖小。开发完一个模块,就可以测试一个模块。妈妈再也不用担心我犯大错了。

2 如何做单元测试

2.1 单元测试框架基础

业界单元测试一般采用基于JUNIT和MockMvc 框架进行, 其中JUNIT 是通用测试框架,主要进行Dao层和Service层测试。MockMvc主要进行Controller层测试.

2.1.1 Junit基本概念

Junit的理解其实很简单,用起来也很方便。关键在于理解几个注解。

  • @Test:将一个普通方法修饰成一个测试方法,以前我们运行程序,需要main方法,而现在,可以直接运行一个被@Test修饰的方法。
    • @Test(excepted=xx.class): xx.class表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的
    • @Test(timeout=毫秒数) :测试方法执行时间是否符合预期
  • @BeforeClass: 会在所有的Class方法执行前被执行,static方法
  • @AfterClass:会在所有的Class方法执行之后进行执行,static方法。通常用于清理工作。
  • @Before:会在每一个@Test测试方法被运行前执行一次
  • @After:会在每一个@Test测试方法运行后被执行一次。通常用于资源关闭。
  • @Ignore:所修饰的测试方法会被测试运行器忽略
  • @RunWith:可以更改测试运行器org.junit.runner.Runner。

总结一下,运行顺序为 BeforeClass---->Before----->Test----->Ater----->AfterClass

2.2 Springboot框架单元测试实战

2.2.1 Maven 引包

pom.xml文件中添加spring-boot-starter-test 依赖

       
            org.springframework.boot
            spring-boot-starter-test
            test
        

该依赖为springboot 的stater,会引入一系列相关包。

Unit Test Jar.png

2.2.2 新建测试源码目录

  • test/java 存放test *.java文件
  • test/resourse 存放yml配置文件

新建好目录后,需要在project structrue中Mark这两个资源目录,否则工程会无法正常识别加载这些文件。

project structrue.png

2.2.3 编写单元测试案例

建议:为了让工程目录友好,可以根据不同的服务,新建不同的pakage。Dao、Service和Controller各新建一个*.java文件。

2.2.3.1 编写测试基类
package com.qinghaihu.springbootmybatis;


import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@Transactional
@Rollback
@RunWith(SpringRunner.class)
public class SpringbootUnitTest {


}

解释

1、@SpringbootTest @RunWith(SpringJUnit4ClassRunner.class)

让测试在Spring容器环境下执行测试

2、@Transactional 开启事务,支持工程代码中配置了的注解式事务
3、@Rollback 设置测试后回滚,默认属性为true,即回滚。保证测试完后,自测数据不污染数据库,从而保证测试案例可以重复执行。

2.2.3.2 编写三层模型测试代码

Springboot项目采用经典三层模型搭建,即dao,service,controller。开发顺序通常是这样,测试顺序也是一般按照这个顺序进行。

从单元测试实践经验来说,dao层单元测试通常不会强制要求,而service和controller则必须进行。

2.2.3.2.1 Service层 测试
package com.qinghaihu.springbootmybatis.user;

import com.qinghaihu.springbootmybatis.SpringbootUnitTest;
import com.qinghaihu.springbootmybatis.dao.SysUserDao;
import com.qinghaihu.springbootmybatis.entity.SysUser;
import com.qinghaihu.springbootmybatis.service.SysUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @ClassName UserServiceTest
 * @Description TODO
 * @Author:Zhang Lianzhong
 * @Date 2020/4/11 5:12 下午
 * @Version 1.0
 **/

public class UserServiceTest extends SpringbootUnitTest {

    @Autowired
    SysUserService sysUserService;

    @Autowired
    SysUserDao sysUserDao;


    @Test
    public void testInsert(){
        SysUser sysUser = new SysUser();
        String id = "5";
        sysUser.setId(id);
        sysUser.setUsername("qinghaihudream");
        sysUser.setPassword("88888887");
        sysUser.setCreatetime(new Date());
        sysUser.setUserdesc("奔跑吧程序猿");
        sysUser.setUserimg("/user/images/qinghaihu.jpg");
        sysUser.setUserstate(-1);

        boolean insertResult = sysUserService.insertUserInfo(sysUser);
        assertTrue(insertResult);

        SysUser queryedUser = sysUserDao.selectByPrimaryKey(id);
        assertEquals(queryedUser.getUsername(),sysUser.getUsername());
    }

    @Test
    public  void testSelect(){
        SysUser sysUser = new SysUser();
        String id = "5";
        sysUser.setId(id);
        sysUser.setUsername("qinghaihudream");
        sysUser.setPassword("88888887");
        sysUser.setCreatetime(new Date());
        sysUser.setUserdesc("奔跑吧程序猿");
        sysUser.setUserimg("/user/images/qinghaihu.jpg");
        sysUser.setUserstate(-1);

        int insertResult = sysUserDao.insert(sysUser);
        assertEquals(insertResult,1);

        SysUser queryedUser = sysUserService.selectById(id);
        assertEquals(queryedUser.getUsername(),sysUser.getUsername());

    }

    @Test
    public void testUpdate() {

        //准备测试数据
        String id = "4";
        String orgUserDesc = "你好程序猿";
        String updateUserDesc = "奔跑吧程序猿";

        SysUser orgSysUser = new SysUser();
        orgSysUser.setId(id);
        orgSysUser.setUsername("qinghaihu");
        orgSysUser.setPassword("88889998");
        orgSysUser.setCreatetime(new Date());
        orgSysUser.setUserdesc(orgUserDesc);
        orgSysUser.setUserimg("/user/images/qinghaihu.jpg");
        orgSysUser.setUserstate(-1);
        int insertCount = sysUserDao.insert(orgSysUser);
        assertEquals(insertCount,1);

        SysUser updateSysUser = new SysUser();
        updateSysUser.setId(id);
        updateSysUser.setUsername("qinghaihu");
        updateSysUser.setPassword("88889998");
        updateSysUser.setCreatetime(new Date());
        updateSysUser.setUserdesc(updateUserDesc);
        updateSysUser.setUserimg("/user/images/qinghaihu.jpg");
        updateSysUser.setUserstate(-1);


        boolean updateResult = sysUserService.updateUserInfo(updateSysUser);
        assertTrue(updateResult);

        //检查数据是否更新
        SysUser queryedUser = sysUserService.selectById(id);
        assertEquals(queryedUser.getUserdesc(),updateUserDesc);


    }

    @Test
    public void testdelete() {
        SysUser sysUser = new SysUser();
        String id = "5";
        sysUser.setId(id);
        sysUser.setUsername("qinghaihudream");
        sysUser.setPassword("88888887");
        sysUser.setCreatetime(new Date());
        sysUser.setUserdesc("奔跑吧程序猿");
        sysUser.setUserimg("/user/images/qinghaihu.jpg");
        sysUser.setUserstate(-1);

        boolean insertResult = sysUserService.insertUserInfo(sysUser);
        assertTrue(insertResult);

        boolean deleteResult = sysUserService.deleteById(id);
        assertTrue(deleteResult);

        //检查数据是否删除
        SysUser queryedUser = sysUserService.selectById(id);
        assertNull(queryedUser);

    }


}

2.2.3.2.2 Controller层测试
package com.qinghaihu.springbootmybatis.user;

import com.qinghaihu.springbootmybatis.SpringbootUnitTest;
import com.qinghaihu.springbootmybatis.dao.SysUserDao;
import com.qinghaihu.springbootmybatis.entity.SysUser;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.Date;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
/**
 * @ClassName UserControllerTest
 * @Description TODO
 * @Author:Zhang Lianzhong
 * @Date 2020/4/11 6:13 下午
 * @Version 1.0
 **/
public class UserControllerTest extends SpringbootUnitTest {

    @Autowired
    SysUserDao sysUserDao;

    @Autowired
    protected WebApplicationContext wac;

    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.webAppContextSetup(wac).build();   //初始化MockMvc对象
    }



    @Test
    public void testQuery() throws Exception {
        //准备测试数据
        String id = "4";
        String userName = "qinghaihu";
        SysUser orgSysUser = new SysUser();
        orgSysUser.setId(id);
        orgSysUser.setUsername(userName);
        orgSysUser.setPassword("88889998");
        orgSysUser.setCreatetime(new Date());
        orgSysUser.setUserdesc("奔跑吧程序猿");
        orgSysUser.setUserimg("/user/images/qinghaihu.jpg");
        orgSysUser.setUserstate(-1);
        int insertCount = sysUserDao.insert(orgSysUser);
        assertEquals(insertCount,1);


        RequestBuilder request = null;

        /*
         * 1、MockMvcRequestBuilders.post("XXX")构造一个post请求。
         * 2、mockMvc.perform执行一个请求。
         * 3、ResultActions.param添加请求传值
         * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE)设置返回类型
         * 5、ResultActions.andExpect添加执行完成后的断言。
         * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
         *    比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
         * 7、ResultActions.andReturn表示执行完成后返回相应的结果。
         */
        //post提交一个people
        request = post("/SysUser/query")
                .param("id",id);
        String json = mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username",is(userName)))
                .andDo(print())
                .andReturn().getResponse().getContentAsString();

    }

}

通过@Autowired WebApplicationContext wac:注入web环境的ApplicationContext容器
在@Before修饰的方法中通过MockMvcBuilders.webAppContextSetup(wac).build()创建一个MockMvc 进行测试。

3 工程源码

https://github.com/zhanglianzhong/sprinboot-tech.git

你可能感兴趣的:(如何对Springboot项目进行单元测试)