SpringBootTest单元测试应用

    前言:

    为什么需要使用单元测试???

  •    保证历史版本代码的正确性,减少重复测试:开发人员实现某个功能或者修补了某个bug,如果有相应的单元测试支持的话,开发人员可以马上通过运行单元测试来验证之前完成的代码是否正确;
  •    提升测试效率:对于依赖多,业务流程复杂,可以通过mock去掉依赖,保证自己编写函数的正确性;
  •    便于后期重构:保单元测试可以为代码的重构提供保障,只要重构代码之后单元测试全部运行通过,那么在很大程度上表示这次重构没有引入新的BUG,当然这是建立在完整、有效的单元测试覆盖率的基础上;
  •    对设计的反馈:单元测试可以反过来指导设计出高内聚、低耦合的模块;   持续集成的一个前提,保证。  

    -- 下面中介绍Spring Boot项目中的单元测试  

目录

  Junit+SpringBootTest单元测试

Service层单元测试

创建测试类:

编写创建好的测试类,具体代码如下:

Controller单元测试

新断言assertThat使用

assertThat 的基本语法如下:

assertThat 的优点

如何使用 assertThat

单元测试回滚

修改默认引擎的步骤


  Junit+SpringBootTest单元测试

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

 dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-test'
}

  引入spring-boot-starter-test后,有如下几个库:

•JUnit - 一个Java语言的单元测试框架。

•Spring Test&Spring Boot Test - 单元和用于系统的集成测试。

•AssertJ - 一个流畅的断言库。 •Hamcrest - matcher对象库(也称为约束或谓词),可以帮助我们创建匹配器对象。

•Mockito - 是一个针对Java的mocking框架。它与EasyMock和jMock很相似,但是通过在执行后校验什么已经被调用,它消除了对期望行为(expectations)的需要。

 •JSONassert - JSON的断言库。 •JsonPath - 用于JSON的XPath。

Service层单元测试

创建测试类:

1、鼠标自动生成

Spring Boot中单元测试类写在在src/test/java目录下,你可以手动创建具体测试类,如果是IDEA,则可以通过IDEA自动创建测试类,如下图:

自动生成测试类如下:

 

2、Alt+Insert快捷键,选择Test...快速生成单元测试类

快速生成Junit单元测试技巧。

Alt+Insert呼出Generate界面,选择Test...,呼出右侧界面,其中①处可以选择单元测试所使用的框架或者二类库,②可以指定该类对应的单元测试类名称,③可以指定该单元测试类继承的父类,一般都是统一继承一个父类,父类里面写单元测试的注解、资源初始化、mock登录、资源释放等操作,④处指定生成的单元测试类包全路径,⑤处可以选择是否生成setUp和tearDown方法,一般③处继承父类就不需要勾选了,⑥处指定生成哪些方法的单元测试方法。

编写创建好的测试类,具体代码如下:

 

@RunWith(SpringRunner.class) // 表明执行程序用spring自己的执行机
@SpringBootTest //表明这是一个springboot测试类,会自动加载springboot主启动程序
public class GsCodeServiceImplTest {
    @Autowired
    GsCodeServiceImpl codeService;
    @Test
    public void add() {
        GsCode gsCode = codeService.add("pages/visitresults/main?venueId=8ab3b87674d2655e0174d274572d0009",
            1, "8ab3b87674d2655e0174d274572d0009");
        Assert.assertNotNull(gsCode);
    }

    @Test
    public void update() {
        Optional<GsCode>  optionalGsCode = codeService.findById("402882817511509201751150eaf00000");
        GsCode code = optionalGsCode.get();
        code.setCodeName("注册码更新");
        GsCode updateCode = codeService.update(code,code.getId());
        Assert.assertEquals(code.getCodeName(), updateCode.getCodeName());
    }

    @Test
    public void findById() {
        Optional<GsCode> optionalGsCode = codeService.findById("402882817511509201751150eaf00000");
        Assert.assertNotNull(optionalGsCode.get());
    }
    @Test
    public void list() {
        List<Map<String, Object>>  codeList = codeService.list(2);
        Assert.assertNotNull(codeList.get(0));
    }

    @Test
    public void pageList() {
        Page<Map<String, Object>> pageList = codeService.pageList(1, PageRequest.of(0, 10));
        Long items = pageList.getTotalElements();
        assertThat(items, allOf(greaterThan(0L), lessThan(11L) ) );
    }
    @Test
    public void delete() {
        codeService.delete("402882817511509201751150eaf00000");
        Optional<GsCode> optionalGsCode = codeService.findById("402882817511509201751150eaf00000");
        Assert.assertNull(optionalGsCode.get());
    }
    @Test
    public void findCodeListByIdList() {
        List<String> codeList = new ArrayList();
        codeList.add("8ab3b87674d2e0b70174d364a7610001");
        List<Map<String, Object>>  resultList = codeService.findCodeListByIdList(codeList);
        Assert.assertSame(1, resultList.size());
    }
}

上面就是最简单的单元测试写法,顶部只要@RunWith(SpringRunner.class))和 SpringBootTest即可,想要执行的时候,鼠标放在对应的方法,右键选择run该方法即可。 测试用例中我使用了assertThat断言,下文中会介绍,也推荐大家使用该断言。

Controller单元测试

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

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

Controller类:

 

 

 

package com.sncy.fuse.controller;
/**
 * @author HJW
 * @Description 二维码Controller
 */
@Slf4j
@RestController
@Api(tags = "二维码 接口")
@RequestMapping("gsCode")
public class GsCodeController {
    @Autowired
    IGsCodeService gsCodeService;

    @GetMapping("/list")
    @ApiOperation(value = "查询二维码信息列表,无分页", notes = "说明信息")
    @ApiImplicitParam(name = "cType", dataType = "int", paramType = "query", value = "二维码类型", required = true)
    public Result<List<Map<String, Object>>> list(@RequestParam int cType) {
        try {
            List<Map<String, Object>> codeInfoList = gsCodeService.list(cType);
            log.info("查询二维码信息列表,无分页,codeInfoList:" + codeInfoList);
            return new Result(ResultCode.CODE_OK.getValue(), ResultCode.CODE_OK.getDesc(), codeInfoList);
        } catch (Exception e) {
            log.error("查询二维码信息列表,无分页 异常!", e);
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "操作失败,请稍后重试!", null);
        }
    }

    @GetMapping("/pageList")
    @ApiOperation(value = "查询二维码信息列表,有分页", notes = "说明信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "cType", dataType = "int", paramType = "query", value = "二维码类型", required = true),
            @ApiImplicitParam(name = "pageNumber", dataType = "int", paramType = "query", value = "当前页数,从0开始", required = true),
            @ApiImplicitParam(name = "pageSize", dataType = "int", paramType = "query", value = "每页条数", required = true)
    })
    public Result<Page<Map<String, Object>>> leaderPageList(int cType, Integer pageNumber, Integer pageSize) {
        try {
            Pageable pageable = PageRequest.of(pageNumber, pageSize);
            Page<Map<String, Object>> pageList = gsCodeService.pageList(cType, pageable);
            log.info("查询二维码信息列表,有分页,pageList:" + pageList);
            return new Result(ResultCode.CODE_OK.getValue(), ResultCode.CODE_OK.getDesc(), pageList);
        } catch (Exception e) {
            log.error("查询二维码信息列表,有分页 异常!", e);
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "操作失败,请稍后重试!", null);
        }
    }

    @GetMapping("/downloadCode/{id}")
    @ApiOperation(value = "下载二维码", notes = "说明信息")
    @ApiImplicitParam(name = "id", dataType = "string", paramType = "query", value = "二维码id", required = true)
    public Result downloadCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String id) {
        log.error("下载二维码! {}", id);
        if (StringUtils.isBlank(id)) {
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "操作失败,请稍后重试!", null);
        }

        try {
            Optional<GsCode> gsCode = gsCodeService.findById(id);
            if (!gsCode.isPresent()) {
                return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "没有id对应的二维码", null);
            }

            String filename = gsCode.get().getCodeName();
            if (StringUtils.isEmpty(filename)) {
                filename = "code";
            }
            //当文件名不是英文名的时候,最好使用url解码器去编码一下,
            filename=URLEncoder.encode(filename,"UTF-8");
            //将响应的类型设置为图片
            String content = gsCode.get().getAddress();
            response.setContentType("image/jpeg");
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            // 通过IO流来传送数据
            URL url= new URL(content);
            URLConnection uc = url.openConnection();
            InputStream input = uc.getInputStream();
            OutputStream output = response.getOutputStream();
            byte[]buff=new byte[1024*10];//可以自己 指定缓冲区的大小
            int len=0;
            while((len=input.read(buff))>-1)
            {
                output.write(buff,0,len);
            }
            //关闭输入输出流
            input.close();
            output.close();
        } catch (Exception e) {
            log.error("下载二维码 异常!", e);
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "操作失败,请稍后重试!", null);
        }
        return new Result(ResultCode.CODE_OK.getValue(), ResultCode.CODE_OK.getDesc(), null);
    }

    /**
     * 批量下载二维码
     *
     * @param request 请求
     * @param response 相应
     * @param codeIdList 二维码id列表
     * @return Result
     */
    @GetMapping("/batchDownloadCode")
    @ApiOperation(value = "下载二维码", notes = "说明信息")
    @ApiImplicitParam(name = "codeIdList", dataType = "List", paramType = "query", value = "二维码id列表", required = true)
    public Result batchDownloadCode(HttpServletRequest request,
        HttpServletResponse response, String[] codeIdList) {
        if (CollectionUtils.isEmpty(Arrays.asList(codeIdList))) {
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "参数校验失败!", null);
        }

        try {
            String zipName = URLEncoder.encode( "qr_code.zip", "UTF-8");
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=\"" + zipName + "\"");
            List<Map<String, Object>> codeList = gsCodeService.findCodeListByIdList(Arrays.asList(codeIdList));
            ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
            for(Map<String, Object> codeMap : codeList) {
                // 二维码中包含的信息
                String codeName = String.valueOf(codeMap.get("codeName"));
                if ("null".equals(codeName)) {
                    codeName = "code" + new Random().nextInt();
                } else {
                    codeName = codeName + new Random().nextInt();
                }
                String content = String.valueOf(codeMap.get("address"));
                ZipEntry entry = new ZipEntry(codeName);
                zipOutputStream.putNextEntry(entry);
                URL url= new URL(content);
                URLConnection uc = url.openConnection();
                InputStream input = uc.getInputStream();
                byte[] buffer = new byte[1024];
                int length = 0;
                while ((length = input.read(buffer)) != -1) {
                    zipOutputStream.write(buffer, 0, length);
                }
                input.close();
            }
            zipOutputStream.flush();
            zipOutputStream.close();
        } catch (Exception e) {
            log.error("下载二维码 异常!", e);
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "操作失败,请稍后重试!", null);
        }
        return null;
    }

    @Transactional
    @PostMapping("/addLoginCode")
    @ApiOperation(value = "新增登录码", notes = "说明信息")
    public Result addLoginCode(@RequestParam String path) {
        log.info("新增登录码,path:" + path);
        try {
            String url = uploadFileToQiNiu(path);
            gsCodeService.addLoginCode(url, 4);
            return new Result(ResultCode.CODE_OK.getValue(), ResultCode.CODE_OK.getDesc(), null);
        } catch (Exception e) {
            log.error("新增登录码 异常!", e);
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return new Result(ResultCode.CODE_Error_BUSINESS.getValue(), "操作失败,请稍后重试!", null);
        }
    }

    private String uploadFileToQiNiu(String filePath) {
        String url = "";
        try {
            File file = new File(filePath + ".png");
            FileInputStream fileInputStream = new FileInputStream(file);
            MultipartFile multipartFile = new MockMultipartFile("copy"+file.getName(),file.getName(), "application/octet-stream",fileInputStream);
            byte[] bytes = multipartFile.getBytes();
            //使用base64方式上传到七牛云
            String fileName = multipartFile.getOriginalFilename().split("\\.")[0];
            log.info("fileName" + fileName);
            url = QiniuCloudUtil.put64image(bytes, fileName);
            log.info("url" + url);
        } catch (Exception e) {
            log.error("传到七牛云 异常!", e);
        }
        return url;
    }

}

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

ackage com.sncy.fuse.controller;

import com.sncy.fuse.entity.GsUser;
import org.junit.After;
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 GsCodeControllerTest {
    @Autowired
    private WebApplicationContext wac;
    private MockMvc mvc;
    private MockHttpSession session;

    @Before
    public void setupMockMvc(){
        mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
        session = new MockHttpSession();
    }

    /**
     * 释放资源
     *
     * @throws Exception
     */
    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void leaderPageList() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/gsCode/pageList?pageNumber=0&pageSize=10&cType=1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void downloadCode() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/gsCode/downloadCode/8ab3b87674d36cc40174d37858b10004")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void batchDownloadCode() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/gsCode/batchDownloadCode/?codeIdList=8ab3b87674d36cc40174d37858b10004&codeIdList=8ab3b87674d388b20174d3ad235f0013")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void addLoginCode() throws Exception {
        mvc.perform(MockMvcRequestBuilders.post("/gsCode/addLoginCode/?path=C:\\Users\\VCEM\\IdeaProjects\\self\\8ab3b87674a03d5a0174a041c3b656565")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .session(session)
        )
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andDo(MockMvcResultHandlers.print());
    }
}

相关api解释:

  1. mockMvc.perform 执行一个请求
  2. MockMvcRequestBuilders.get("XXX")构造一个请求,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.param() 添加请求传值
  7. ResultActions.accept()  设置返回类型
  8. ResultActions.andExpect 添加执行完成后的断言
  9.  ResultActions.andExpect(MockMvcResultMatchers.status().isOk())方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过
  10. ResultActions.andDo 添加一个结果处理器,表示要对结果做点什么事情。
  11. ResultActions.andReturn 表示执行完成后,返回响应的结果。
  12. MockMvcResultHandlers.print()输出整个响应结果信息

新断言assertThat使用

JUnit 4.4 结合 Hamcrest 提供了一个全新的断言语法——assertThat。可以只使用 assertThat 一个断言语句,结合 Hamcrest 提供的匹配符,就可以表达全部的测试思想。

assertThat 的基本语法如下:

assertThat 基本语法

assertThat( [value], [matcher statement] );

  1. value 是接下来想要测试的变量值;
  2. 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 匹配符,用户可以使用匹配符规定的匹配准则精确的指定一些想设定满足的条件,具有很强的易读性,而且使用起来更加灵活。如下表所示:

使用匹配符 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));),使得代码更加直观、易读。

优点 4:可以将这些 Matcher 匹配符联合起来灵活使用,达到更多目的。如下表所示:

Matcher 匹配符联合使用:

// 联合匹配符not和equalTo表示“不等于”
assertThat( something, not( equalTo( "developer" ) ) );
// 联合匹配符not和containsString表示“不包含子字符串”
assertThat( something, not( containsString( "Works" ) ) );
// 联合匹配符anyOf和containsString表示“包含任何一个子字符串”
assertThat(something, anyOf(containsString("developer"),

 

优点 5:错误信息更加易懂、可读且具有描述性(descriptive)
JUnit 4.4 以前的版本默认出错后不会抛出额外提示信息,如:

assertTrue( s.indexOf("developer") > -1 || s.indexOf("Works") > -1 );

如果该断言出错,只会抛出无用的错误信息,如:junit.framework.AssertionFailedError:null。
如果想在出错时想打印出一些有用的提示信息,必须得我们另外手动写,如:

assertTrue( "Expected a string containing 'developer' or 'Works'",
    s.indexOf("developer") > -1 || s.indexOf("Works") > -1 );

非常的不方便,而且需要额外代码。
JUnit 4.4 会默认自动提供一些可读的描述信息,如清下表所示:

String s = "hello world!";
assertThat( s, anyOf( containsString("developer"), containsString("Works") ) );
// 如果出错后,系统会自动抛出以下提示信息:
java.lang.AssertionError:
Expected: (a string containing "developer" or a string containing "Works")
got: "hello world!

如何使用 assertThat

JUnit 4.4 自带了一些 Hamcrest 的匹配符 Matcher,但是只有有限的几个,在类 org.hamcrest.CoreMatchers 中定义,要想使用他们,必须导入包 org.hamcrest.CoreMatchers.*。

下表列举了大部分 assertThat 的使用例子:

字符相关匹配符
/**equalTo匹配符断言被测的testedValue等于expectedValue,
* equalTo可以断言数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
*/
assertThat(testedValue, equalTo(expectedValue));
/**equalToIgnoringCase匹配符断言被测的字符串testedString
*在忽略大小写的情况下等于expectedString
*/
assertThat(testedString, equalToIgnoringCase(expectedString));
/**equalToIgnoringWhiteSpace匹配符断言被测的字符串testedString
*在忽略头尾的任意个空格的情况下等于expectedString,
*注意:字符串中的空格不能被忽略
*/
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
/**containsString匹配符断言被测的字符串testedString包含子字符串subString**/
assertThat(testedString, containsString(subString) );
/**endsWith匹配符断言被测的字符串testedString以子字符串suffix结尾*/
assertThat(testedString, endsWith(suffix));
/**startsWith匹配符断言被测的字符串testedString以子字符串prefix开始*/
assertThat(testedString, startsWith(prefix));
一般匹配符
/**nullValue()匹配符断言被测object的值为null*/
assertThat(object,nullValue());
/**notNullValue()匹配符断言被测object的值不为null*/
assertThat(object,notNullValue());
/**is匹配符断言被测的object等于后面给出匹配表达式*/
assertThat(testedString, is(equalTo(expectedValue)));
/**is匹配符简写应用之一,is(equalTo(x))的简写,断言testedValue等于expectedValue*/
assertThat(testedValue, is(expectedValue));
/**is匹配符简写应用之二,is(instanceOf(SomeClass.class))的简写,
*断言testedObject为Cheddar的实例
*/
assertThat(testedObject, is(Cheddar.class));
/**not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object*/
assertThat(testedString, not(expectedString));
/**allOf匹配符断言符合所有条件,相当于“与”(&&)*/
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16) ) );
/**anyOf匹配符断言符合条件之一,相当于“或”(||)*/
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
数值相关匹配符
/**closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内*/
assertThat(testedDouble, closeTo( 20.0, 0.5 ));
/**greaterThan匹配符断言被测的数值testedNumber大于16.0*/
assertThat(testedNumber, greaterThan(16.0));
/** lessThan匹配符断言被测的数值testedNumber小于16.0*/
assertThat(testedNumber, lessThan (16.0));
/** greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0*/
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
/** lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0*/
assertThat(testedNumber, lessThanOrEqualTo (16.0));
集合相关匹配符
/**hasEntry匹配符断言被测的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项*/
assertThat(mapObject, hasEntry("key", "value" ) );
/**hasItem匹配符表明被测的迭代对象iterableObject含有元素element项则测试通过*/
assertThat(iterableObject, hasItem (element));
/** hasKey匹配符断言被测的Map对象mapObject含有键值“key”*/
assertThat(mapObject, hasKey ("key"));
/** hasValue匹配符断言被测的Map对象mapObject含有元素值value*/
assertThat(mapObject, hasValue(value));

单元测试回滚

 

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

Test
@Transactional
public void update() {
    Optional<GsCode>  optionalGsCode = codeService.findById("402882817511509201751150eaf00000");
    GsCode code = optionalGsCode.get();
    code.setCodeName("注册码更新");
    GsCode updateCode = codeService.update(code,code.getId());
    Assert.assertEquals(code.getCodeName(), updateCode.getCodeName());
}

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

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

修改默认引擎的步骤

查看MySQL当前默认的存储引擎:

 

你要看 gs_code表用了什么引擎(在显示结果里参数engine后面的就表示该表当前用的存储引擎):

show create table gs_code;

 

 CREATE TABLE `gs_code` (

  `id` varchar(35) NOT NULL DEFAULT '' COMMENT '主键',

  `cType` int(1) DEFAULT '0' COMMENT '类型(0注册码  1参观码   2车辆码 3酒店码)',

  `codeName` varchar(35) DEFAULT NULL COMMENT '二维码名称',

  `mainId` varchar(35) DEFAULT NULL COMMENT '对应主体id',

  `address` varchar(300) DEFAULT NULL COMMENT '二维码地址',

  `createTime` varchar(20) DEFAULT NULL COMMENT '创建时间',

  `updateTime` varchar(20) DEFAULT NULL COMMENT '修改时间',

  `isDelete` int(1) DEFAULT '0' COMMENT '是否删除(0未删除,1删除)',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='二维码表'

 

将gs_code表修为InnoDB存储引擎(也可以此命令将InnoDB换为MyISAM):

 ALTER TABLE gs_code ENGINE=INNODB;

 

如果要更改整个数据库表的存储引擎,一般要一个表一个表的修改,比较繁琐,可以采用先把数据库导出,得到SQL,把MyISAM全部替换为INNODB,再导入数据库的方式。
转换完毕后重启mysql

service mysqld restart

 

 

 

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot的单元测试是开发过程中不可或缺的一部分,它允许开发者对应用程序的各个模块进行独立的测试,确保每个组件的功能正确无误。在Spring Boot中,测试主要依赖于Spring Test框架,特别是JUnit和Mockito等工具。 以下是Spring Boot单元测试的一些关键概念和步骤: 1. **测试类**:通常创建一个继承自`SpringBootTest`或其子类的测试类,如`@RunWith(SpringRunner.class)`注解用于使用Spring的测试运行器。 ```java import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class MyServiceTest { // 测试代码... } ``` 2. **@Autowired注解**:Spring会自动注入被测试对象(通常是`@Component`或`@Service`)到测试类中,便于进行依赖注入的测试。 ```java @Autowired private MyService myService; ``` 3. **@MockBean或@SpyBean**:Spring Test提供了`Mockito`库的支持,可以模拟(mock)或部分地模拟对象的行为,便于测试特定的方法。 4. **@Test方法**:定义测试用例,调用被测试对象的方法并验证结果。例如,使用`assertThat`检查预期输出。 ```java @Test public void testMyMethod() { // 预期结果 List<String> expected = Arrays.asList("foo", "bar"); // 调用方法 List<String> result = myService.myMethod(); // 验证结果 assertThat(result, is(equalTo(expected))); } ``` 5. **@Transactional**:如果你的测试涉及到数据库操作,可以使用`@.Transactional`来保证测试环境的一致性,事务会在测试开始前开启并在测试结束后回滚。 6. **@SpringBootTest(classes = MyClass.class)**:如果你想测试整个Spring应用上下文,可以指定要加载的类或配置。 相关问题: 1. Spring Boot的单元测试和集成测试有什么区别? 2. 如何在Spring Boot中编写数据库相关的单元测试? 3. `@SpringBootTest`和`@WebMvcTest`的区别是什么?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值