单元测试Mock工具TestableMock使用

2023-10-27

单元测试原则

单元测试必须遵循AIR(Automatic, Independent, Repeatable)原则:单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。

  • Automatic(自动化):单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中禁止使用System.out来进行人肉验证,必须使用assert来验证。
  • Independent(独立性):保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
  • Repeatable(可重复):单元测试是可以重复执行的,不能受到外界环境的影响。单元测试通常会被放到持续集成中,每次有代码check in时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。

Mock工具对比

目前主要的Mock工具主要有Mockito、Spock、PowerMock和JMockit等,基本差异如下:

工具 原理 最小Mock单元 对被Mock方法的限制 上手难度 IDE支持
Mockito 动态代理 不能Mock私有/静态和构造方法 较容易 很好
Spock 动态代理 不能Mock私有/静态和构造方法 较复杂 一般
PowerMock 自定义类加载器 任何方法皆可 较复杂 较好
JMockit 运行时字节码修改 不能Mock构造方法(new操作符) 较复杂 一般
TestableMock 运行时字节码修改 方法 任何方法皆可 很容易 一般

本文以TestableMock为例,针对单元测试中常出现的几种场景进行测试。

TestableMock 官方文档:https://alibaba.github.io/testable-mock/#/

TestableMock GitHub地址:https://github.com/alibaba/testable-mock

备注:单元测试基础学习:https://www.yuque.com/atguigu/springboot/ksndgx

本文使用代码样例环境:Spring Boot 2.5.7、TestableMock 0.70、MySQL 5.7.32、MyBatis 2.2.0、H2 1.4.200等,详见pom.xml文件

数据初始化准备

创建数据库goodsdb,创建表并初始化数据:

DROP TABLE IF EXISTS book_t;
CREATE TABLE book_t (
  book_id int(11) AUTO_INCREMENT PRIMARY KEY,
  book_name varchar(32) NOT NULL,
  book_price decimal(5,2) NOT NULL
);
INSERT INTO book_t VALUES (1, '安徒生童话', 99.99);
INSERT INTO book_t VALUES (2, 'MySQL实战教程', 88.88);

创建SpringBoot项目

官方地址:https://start.spring.io/

pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/>
    </parent>
    <groupId>com.lwy.it</groupId>
    <artifactId>testablemock-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>testablemock-demo</name>
    <description>Demo project for Spring Boot With TestableMock</description>
    <properties>
        <java.version>1.8</java.version>
        <testable.version>0.7.0</testable.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 单元测试相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.testable</groupId>
            <artifactId>testable-all</artifactId>
            <version>${testable.version}</version>
            <scope>test</scope>
        </dependency>
        <!-- 内存数据库,用于Mock MySQL-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.200</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <argLine>
                        -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar
                    </argLine>
                </configuration>
            </plugin>
            <!-- 插件介绍:http://maven.apache.org/surefire/maven-surefire-report-plugin/ -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>3.0.0-M5</version>
                <configuration>
                    <outputDirectory>${basedir}/target/report</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

代码结构

本文采用MVC结构:

创建ResultVO对象,用于封装返回结果给前端:

package com.lwy.it;

import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class ResultVO<T> implements Serializable {
    private List<T> data;
    private String message;

    public ResultVO() {
    }

    public ResultVO(List<T> data) {
        this.data = data;
    }

    public ResultVO(List<T> data, String message) {
        this.data = data;
        this.message = message;
    }
}

vo对象:BookVO

package com.lwy.it.book.vo;

import lombok.Data;

import java.io.Serializable;

@Data
public class BookVO implements Serializable {
    private Integer bookId;
    private String bookName;
    private Double bookPrice;
}

控制器层controller对象:BookController

package com.lwy.it.book.controller;

import com.lwy.it.ResultVO;
import com.lwy.it.book.service.BookService;
import com.lwy.it.book.vo.BookVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping("/books")
    public ResultVO<BookVO> findAllBook() {
        return bookService.getAllBook();
    }

    /**
     * 用于模拟远程服务,通过RestTemplate调用
     * 接口服务地址:http://localhost:8080/mock/book
     *
     * @return ResultVO<BookVO>
     */
    @GetMapping("/book")
    public ResultVO<BookVO> exclusiveBook() {
        BookVO bookVO = new BookVO();
        bookVO.setBookId(100000);
        bookVO.setBookName("专属丛书:十万个为什么");
        bookVO.setBookPrice(998d);
        ResultVO<BookVO> resultVO = new ResultVO<>(Arrays.asList(bookVO));
        resultVO.setMessage("获取专属丛书成功");
        return resultVO;
    }

}

服务层service对象:BookService

package com.lwy.it.book.service;

import com.lwy.it.ResultVO;
import com.lwy.it.book.configuration.BookConfiguration;
import com.lwy.it.book.dao.BookDao;
import com.lwy.it.book.vo.BookVO;
import com.lwy.it.utils.DataUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Objects;

@Slf4j
@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    @Autowired
    private BookConfiguration bookConfiguration;

    @Autowired
    private RestTemplate restTemplate;

    public ResultVO<BookVO> getAllBook() {
        List<BookVO> list = bookDao.getAllBook();
        ResultVO<BookVO> resultVO = DataUtils.convert(list);
        // 调用私有方法
        ResultVO<BookVO> vo = this.getExclusiveBook();
        log.info("获取到远程服务结果为:{}", vo);
        return resultVO;
    }

    // 用于模拟远程服务接口获取数据过程
    private ResultVO<BookVO> getExclusiveBook() {
        ResultVO<BookVO> resultVO = null;
        String url = bookConfiguration.getServerUrl();
        log.info("获取到的配置服务URL地址为:{}", url);
        // 返回结果是泛型类型,使用ParameterizedTypeReference进行包装
        ParameterizedTypeReference<ResultVO<BookVO>> reference = new ParameterizedTypeReference<ResultVO<BookVO>>() {
        };
        ResponseEntity<ResultVO<BookVO>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, reference);
        if (Objects.equals(responseEntity.getStatusCode(), HttpStatus.OK)) {
            resultVO = responseEntity.getBody();
            log.info("获取到的结果为:{}", resultVO);
        } else {
            log.error("获取数据失败");
        }
        return resultVO;
    }
}

备注:RestTemplate基础学习:https://mp.weixin.qq.com/s/jIZCFOW4iy0j-epKVKQpOQ

数据库持久层dao对象:BookDao

package com.lwy.it.book.dao;

import com.lwy.it.book.vo.BookVO;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface BookDao {
    List<BookVO> getAllBook();
}

对应mapper文件:book.mysql.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lwy.it.book.dao.BookDao">
    <select id="getAllBook" resultType="com.lwy.it.book.vo.BookVO">
        SELECT
            book_id AS bookId,
            book_name AS bookName,
            book_price AS bookPrice
        FROM
            book_t
    </select>
</mapper>

相关配置信息

application.properties

server.port=8080

server.servlet.context-path=/mock

#MyBatis配置
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:mapper/*.xml

#MySQL配置
spring.datasource.url=jdbc:mysql://localhost:3306/goodsdb?allowMultiQueries=true&serverTimezone=GMT&characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

#配置远程服务地址
book.server.url=${bookUrl}

创建配置类,用于接收book.server.url变量参数,BookConfiguration

package com.lwy.it.book.configuration;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Data
@Configuration
public class BookConfiguration {

    @Value("${book.server.url}")
    private String serverUrl;

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }

}

工具类:DataUtils

package com.lwy.it.utils;

import com.lwy.it.ResultVO;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;

public class DataUtils {

    /**
     * 用于数据包装转换
     *
     * @param list 泛型结合
     * @param <T>  泛型类型
     * @return ResultVO<T>
     */
    public static <T> ResultVO<T> convert(List<T> list) {
        ResultVO<T> resultVO = new ResultVO<>();
        if (CollectionUtils.isEmpty(list)) {
            resultVO.setData(Collections.emptyList());
            resultVO.setMessage("数据为空");
        } else {
            resultVO.setData(list);
            resultVO.setMessage("数据不为空");
        }
        return resultVO;
    }

}

以上为服务的相关代码,均在src/main目录下面的目录中。

启动服务(要添加环境变量参数:bookUrl=http://localhost:8080/mock/book):访问http://localhost:8080/mock/books,得到数据库中数据:

{"data":[{"bookId":1,"bookName":"安徒生童话","bookPrice":99.99},{"bookId":2,"bookName":"MySQL实战教程","bookPrice":88.88}],"message":"数据不为空"}

编写单元测试代码

单元测试文件均位于在src/test目录下:

数据持久层单元测试

对于Dao层(数据库相关的查询,更新,删除等)操作,使用嵌入式内存数据库H2 Database验证逻辑。和数据库相关的单元测试,不给数据库造成脏数据。不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。 反例:删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数据并不符合业务插入规则,导致测试结果异常。

针对BookDao访问数据库进行单元测试,需要在src/test/resources目录下新建application.properties文件,内容如下

server.port=8080
server.servlet.context-path=/mock
#MyBatis配置
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:mapper/*.xml

########################数据更改###########################
#内存数据库H2以MYSQL模式运行,初始化时执行classpath:initdb.sql脚本,详情参考:http://www.h2database.com/html/features.html
spring.datasource.url=jdbc:h2:mem:goodsdb;MODE=MYSQL;INIT=RUNSCRIPT FROM 'classpath:initdb.sql'
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=
spring.datasource.password=
#########################数据更改##########################

#自定义变量配置
book.server.url=${bookUrl}

H2数据库初始化脚本initdb.sql文件如下:

DROP TABLE IF EXISTS book_t;
CREATE TABLE book_t (
                        book_id int(11) AUTO_INCREMENT PRIMARY KEY,
                        book_name varchar(32) NOT NULL,
                        book_price decimal(5,2) NOT NULL,
                        PRIMARY KEY (book_id)
);

INSERT INTO book_t VALUES (1, '测试数据1', 99.99);
INSERT INTO book_t VALUES (2, '测试数据2', 88.88);
INSERT INTO book_t VALUES (3, '测试数据2', 77.77);
INSERT INTO book_t VALUES (4, '测试数据2', 66.66);
INSERT INTO book_t VALUES (5, '测试数据2', 55.55);
INSERT INTO book_t VALUES (6, '测试数据2', 44.44);

BookDao对应单元测试BookDaoTest代码如下(包路径一致):

package com.lwy.it.book.dao;

import com.lwy.it.book.vo.BookVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

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

@Slf4j
@SpringBootTest
public class BookDaoTest {

    @Autowired
    private BookDao bookDao;

    @Test
    @DisplayName("getAllBook")
    public void getAllBook_test() {
        List<BookVO> list = bookDao.getAllBook();
        log.info("结果为:{}", list);
        assertEquals(6, list.size());
    }
}

启动测试方法(要添加环境变量参数:bookUrl=http://localhost:8080/mock/book),日志如下,执行成功。我们发现数据被H2数据库中我们初始化的数据进行了替代。

2021-12-01 16:25:31.458  INFO 21644 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-12-01 16:25:31.919  INFO 21644 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-12-01 16:25:31.983  INFO 21644 --- [           main] com.lwy.it.book.dao.BookDaoTest          : 结果为:[BookVO(bookId=1, bookName=测试数据1, bookPrice=99.99), BookVO(bookId=2, bookName=测试数据2, bookPrice=88.88), BookVO(bookId=3, bookName=测试数据2, bookPrice=77.77), BookVO(bookId=4, bookName=测试数据2, bookPrice=66.66), BookVO(bookId=5, bookName=测试数据2, bookPrice=55.55), BookVO(bookId=6, bookName=测试数据2, bookPrice=44.44)]

服务层单元测试

因为前面场景服务层依赖外部服务等,测试案例应不依赖环境,所以我们需要把依赖Mock掉

我们使用TestableMock工具,按照官方文档进行操作:https://alibaba.github.io/testable-mock/#/

备注:这里的版本我们使用0.7.x版本,<testable.version>0.7.0</testable.version>,对应泛型Mock更加便捷。

单元测试代码如下:

package com.lwy.it.book.service;

import com.alibaba.testable.core.annotation.MockInvoke;
import com.alibaba.testable.core.tool.PrivateAccessor;
import com.lwy.it.ResultVO;
import com.lwy.it.book.dao.BookDao;
import com.lwy.it.book.vo.BookVO;
import com.lwy.it.utils.DataUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

@Slf4j
@SpringBootTest
public class BookServiceTest {

    @Autowired
    private BookService bookService;

    // 前置步骤,准备Mock容器
    public static class Mock {
        // 放置Mock方法的地方,按照官方示例 把Mock方法访问控制都改成private

        /**
         * 静态方法&泛型类型方法进行Mock
         * DataUtils类中public static <T> ResultVO<T> convert(List<T> list)
         *
         * @param list 泛型集合
         * @param <T>  泛型类型
         * @return ResultVO<T>
         */
        @MockInvoke(targetClass = DataUtils.class)
        private static <T> ResultVO<T> convert(List<T> list) {
            log.info("~~~~~~泛型&静态方法被执行~~~~~~");
            return new ResultVO<>(Collections.emptyList());
        }

        /**
         * Mock掉数据访问层BookDao的List<BookVO> getAllBook()方法
         *
         * @return List<BookVO>
         */
        @MockInvoke(targetClass = BookDao.class)
        private List<BookVO> getAllBook() {
            BookVO bookVO = new BookVO();
            bookVO.setBookId(10000);
            bookVO.setBookName("测试数据");
            bookVO.setBookPrice(99.99d);
            log.info("~~~~~~Dao层访问被Mock替代~~~~~~");
            return new ArrayList<BookVO>() {{
                add(bookVO);
            }};
        }

        /**
         * BookService getExclusiveBook()方法中调用RestTemplate的方法,外部依赖需要Mock掉
         * public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException
         *
         * @param url           访问url
         * @param method        get/post访问方式等
         * @param requestEntity 请求参数
         * @param responseType  返回类型(因为涉及泛型)
         * @param uriVariables
         * @param <T>           泛型类型
         * @return
         * @throws RestClientException
         */
        @MockInvoke(targetClass = RestTemplate.class)
        private <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
                                               ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
            log.info("~~~~~~调用RestTemplate方法被Mock替代~~~~~~");
            return new ResponseEntity<T>(HttpStatus.OK);
        }
    }

    @Test
    @DisplayName("public方法:getAllBook单元测试")
    public void getAllBook_test() {
        ResultVO<BookVO> resultVO = bookService.getAllBook();
        log.info("ResultVO结果为:{}", resultVO);
        assertEquals(0, resultVO.getData().size());

    }

    /**
     * 对私有方法进行单元测试
     */
    @Test
    @DisplayName("private方法:getExclusiveBook单元测试")
    public void getExclusiveBook_test() {
        ResultVO<BookVO> resultVO = PrivateAccessor.invoke(bookService, "getExclusiveBook");
        log.info("ResultVO结果为:{}", resultVO);
        assertNull(resultVO);
    }

}

执行验证逻辑:

执行getAllBook_test测试用例(要添加环境变量参数:bookUrl=http://localhost:8080/mock/book),结果如下,实际调用过程中被替代

2021-12-01 16:56:32.623  INFO 23380 --- [           main] com.lwy.it.book.service.BookServiceTest  : ~~~~~~Dao层访问被Mock替代~~~~~~
2021-12-01 16:56:32.625  INFO 23380 --- [           main] com.lwy.it.book.service.BookServiceTest  : ~~~~~~泛型&静态方法被执行~~~~~~
2021-12-01 16:56:32.625  INFO 23380 --- [           main] com.lwy.it.book.service.BookService      : 获取到的配置服务URL地址为:http://localhost:8080/mock/book
2021-12-01 16:56:32.628  INFO 23380 --- [           main] com.lwy.it.book.service.BookServiceTest  : ~~~~~~调用RestTemplate方法被Mock替代~~~~~~
2021-12-01 16:56:32.638  INFO 23380 --- [           main] com.lwy.it.book.service.BookService      : 获取到的结果为:null
2021-12-01 16:56:32.638  INFO 23380 --- [           main] com.lwy.it.book.service.BookService      : 获取到远程服务结果为:null
2021-12-01 16:56:32.638  INFO 23380 --- [           main] com.lwy.it.book.service.BookServiceTest  : ResultVO结果为:ResultVO(data=[], message=null)

执行getExclusiveBook_test测试用例(要添加环境变量参数:bookUrl=http://localhost:8080/mock/book),结果如下,实际调用过程中被替代

2021-12-01 17:08:10.378  INFO 7740 --- [           main] com.lwy.it.book.service.BookService      : 获取到的配置服务URL地址为:http://localhost:8080/mock/book
2021-12-01 17:08:10.383  INFO 7740 --- [           main] com.lwy.it.book.service.BookServiceTest  : ~~~~~~调用RestTemplate方法被Mock替代~~~~~~
2021-12-01 17:08:10.394  INFO 7740 --- [           main] com.lwy.it.book.service.BookService      : 获取到的结果为:null
2021-12-01 17:08:10.394  INFO 7740 --- [           main] com.lwy.it.book.service.BookServiceTest  : ResultVO结果为:null

执行所有测试用例:

mvn clean test -DbookUrl=http://localhost:8080/mock/book
若涉及多profile文件,则添加-P参数

执行所有测试用例并生成报告:

mvn clean surefire-report:report -DbookUrl=http://localhost:8080/mock/book

在target/report/(pom.xml文件配置的${basedir}/target/report)目录下生成了surefire-report.html文件,用浏览器打开即可。

如果需要覆盖率信息,则需要使用Jacoco。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单元测试Mock工具TestableMock使用 的相关文章

随机推荐

  • 使用阿里云OSS实现文件的上传、下载、删除及修改功能

    一 配置OSS相关配置信息 1 要配置 OSS 相关配置信息 您可以按照以下步骤操作 登录阿里云控制台 进入 OSS 控制台 创建一个新的 OSS Bucket 并记录下以下信息 Bucket 名称 Bucket 所属地域 AccessKe
  • 如何解决git上传文件出错[rejected] master -> master (fetch first) error: failed to push some refs to '

    rejected master gt master fetch first error failed to push some refs to git gitee co 上传到码云的时候 报了这个错误 rejected master gt
  • 使用sklearn学习多项式回归(三)

    目录 1 什么是线性 1 1 变量之间的线性关系 1 2 数据间的线性与非线性 1 3 线性模型与非线性模型 1 4 使用分箱处理非线性问题 2 多项式回归PolynomialFeatures 2 1 什么是多项式回归 2 2 多项式回归处
  • 使用 Openssl 验证自签名证书

    原文地址 http blog csdn net kmyhy article details 6546072 iOS的 security framework 框架前面已经介绍 这个框架提供有限的功能 使用它能做到的 比你想象的要少 笔者一直想
  • html input 禁用缓存

    多数浏览器默认会缓存input的值 只有使用ctl F5强制刷新的才可以清除缓存记录 如果不想让浏览器缓存input的值 有2种方法 input 的属性autocomplete 默认为on 其含义代表是否让浏览器自动记录之前输入的值 很多时
  • word编辑公式简单方法

    安装Python包 pix2tex 在终端输入 pip install pix2tex gui i https pypi doubanio com simple 就可以安装 安装好以后 在终端输入pix2tex gui就可以启动 第一次启动
  • Android多级树形选择列表案例 - 手把手教你快速实现

    公司项目中有一个选择联系人的界面 一看里面关系极其复杂 最多时有5层关系嵌套 层数还不一定 有的是第五级是人员 有的是第四级是人员 崩溃中 原来的实现方式是分了三个Activity去分别加载 个人觉得太过臃肿麻烦 选个人要调四次页面 太繁琐
  • Vite原理学习之预编译

    前言 Vite是下一代的前端开发与构建工具 为什么称为下一代 根本原因在于其基于原生ES Module 在目前的前端工程化生态中 webpack rollup esbuild等非常流行 而Vite真是构建在一些流行的技术上 Vite的出现实
  • mysql按升序创建索引_MySQL中如何使用索引

    原标题 MySQL中如何使用索引 者 Airy 在数据分析之路狂奔 立志成为大咖级人物 前言 学完基础的MySQL知识 以及MySQL的增删改查 我们要学习一些性能方面的东西 今天来讲一下索引 Index 索引 在关系数据库中 索引是一种单
  • BUUCTF学习笔记-EasySQL

    BUUCTF学习笔记 EasySQL 时间 2020 03 14 考点 堆叠注入 SQL模式 打开是一个输入框 提交参数查询 随意提交几个参数 发现存在堆叠注入 输入1显示Array 0 gt 1 输入2显示Array 0 gt 1 输入2
  • SQL基础之增、删、改、查

    文章目录 SQL基础之增 删 改 查 SELECT INSERT UPDATE DELETE 参考 SQL基础之增 删 改 查 SQL Structured Query Language SELECT 查 SELECT FROM 整张表查询
  • ACwing算法基础课全程笔记(2021年8月12日开始重写+优化)

    更好的阅读体验 基础模板 2021年8月12日开始对基础课笔记进行重写 优化 请大家支持AcWing正版 购买网课能让自己获得更好的学习体验哦 链接 https www acwing com about 比赛常用技巧及库函数 1 快速读 快
  • win11 install wsl2

    在 Windows 11 上安装 WSL 2 的步骤如下 首先 确保你的 Windows 版本是 Windows 11 或者更新的版本 并且支持 WSL 2 然后 打开 设置 应用 在 更新与安全 页面的 开发人员选项 中 启用 Windo
  • 用200行C语言代码写出一个贪吃蛇——1.0(基本版)

    1 设计思路 总的来说 贪吃蛇这个小游戏涉及到的东西不多 但是对逻辑思维是比较吃基本功的 贪吃蛇 显示给我们看的有三部分 蛇 食物 地图边界 我们可以用一个二维数组来标记这些部分 例如这里我创建了一个 5 10 的二维数组 其中 1 表食物
  • 手写模拟Spring的底层原理2.1

    先来引入两个问题 第一个懒加载还是立即加载的问题 这个问题还是在于就是说 当我们xml配置了一个对象bean的时候 它在spring容器里面是什么时候开始会给我们创建这个对象 那如果我们想对某个对象启动懒加载 可以添加 lazy这个注解 这
  • Fiddler抓包工具保姆级使用教程(超详细)

    超文本传输协议 HTTP 是一个简单的请求 响应协议 其主要是基于TCP来实现的 可以通过Chrome开发者工具或者Wireshark或者Fiddler抓包 以便分析 HTTP 请求 响应的细节 本篇博客主要谈论如何使用Fiddler抓取H
  • vector 二维数组

    动态数组 int p p new int m 注意 int m 表示一个有m个元素的指针数组 p new int 8 表示分配一个int空间并初始化为8 for int i 0 i lt m i p i new int 5 利用Vector
  • OpenGL Shading language学习总结

    这篇文章是根据
  • 基于Ymodem协议的STM32串口IAP的实现(IAP + APP + 上位机)

    基于Ymodem协议的STM32串口IAP的实现 IAP APP 上位机 HavenXie关注 0 1832017 06 05 00 35 09字数 1 373阅读 10 038 1 什么是IAP IAP In Application Pr
  • 单元测试Mock工具TestableMock使用

    单元测试原则 单元测试必须遵循AIR Automatic Independent Repeatable 原则 单元测试在线上运行时 感觉像空气 AIR 一样感觉不到 但在测试质量的保障上 却是非常关键的 好的单元测试宏观上来说 具有自动化