秒懂如何使用SpringBoot+Junit4进行单元测试(上)

简介: 秒懂如何使用SpringBoot+Junit4进行单元测试(上)

正文


一、目标


学会基于AssertJ的断言技术;

学会基于AssertJ-DB的数据库断言技术;

学会基于JMockit的mock技术;

学会内存和数据库的造数;

学会集成Maven进行单元测试、集成测试的执行;

学会查看测试覆盖率;


二、断言技术


断言库包含很多,比如junit自带的、hamcrest等,这里推荐使用AssertJ,看它的官网就知道了,宣称fluent assertions java library。


2.1 核心库断言


AssertJ的断言采用assertThat(result)的形式,等同于then(result),这两种方式使用上没有区别;我们需要在pom中引入如下依赖:

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>


下面列出常见的断言用法,其它的用法可以参考依赖库学习使用。


对文本的断言;

assertThat(result).isEqualTo("apple");
assertThat(result).isEqualToIgnoringCase("apple");
assertThat(result).contains("apple");
assertThat(result).containsIgnoringCase("apple");
assertThat(result).startsWith("apple");
assertThat(result).matches("^[A-Za-z0-9]{8}$");
assertThat(result).hasSize(10);
assertThat(result).containsSequence("a", "p", "l");
...


对数字的断言;

assertThat(result).isGreaterThanOrEqualTo(100);
assertThat(result).isCloseTo(100.0, Offset.offset(0.000001));
assertThat(result).isBetween(90.0, 91.0),
assertThat(result).isNaN();
...


对日期的断言;

assertThat(result).isAfter(startDate);
assertThat(result).isBefore("2020-01-01");
assertThat(result).isInSameMonthAs("2019-12-01");
...


对集合的断言;

assertThat(result).hasSize(3);
assertThat(result).contains("apple", "orange");
assertThat(result).doesNotcontain("apple", "orange");
assertThat(result).containsExactly("apple", "orange");
assertThat(result).startsWith("apple");
assertThat(result).endsWith("orange");
assertThat(result).doesNotContainNull();
assertThat(result).doesNotHaveDuplicates();
assertThat(result).isNotEmpty();
assertThat(result).isNullOrEmpty();
...
assertThat(result).hasSize(2);
assertThat(result).containsEntry("apple", "12");
assertThat(result).containsKeys("apple", "orange");
assertThat(result).containsOnlyKeys("apple", "orange");
assertThat(result).containsValues("apple", "orange");
...


对对象的断言;

assertThat(result).isEqualToComparingOnlyGivenFields(obj1, "name", "weight");
assertThat(result).isEqualToIgnoringGivenFields(obj1,"name", "weight");
assertThat(result).isEqualToIgnoringNullFields(obj1);
...


2.2 数据库断言


AssertJ-Core只适合为单元测试使用,如果要进行集成测试,或者只测试DAO层的SQL执行结果,就无能为力了,这是就需要用到AssertJ-DB,首先我们需要在pom中引入如下的依赖:

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-db</artifactId>
            <version>1.3.0</version>
            <scope>test</scope>
        </dependency>


下面是一些常用的功能:


数据源

如果我们想使用SpringBoot项目中配置的数据源,比如在application.properties中的数据库配置项:

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.username=postgre
spring.datasource.password=postgre
spring.datasource.driver-class-name=org.postgresql.Driver

那么我们就需要在运行该单元测试的时候启动整个Spring Boot工程,首先需要先建立一个测试基类:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = DailyWorkServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Transactional
@Rollback
public class BaseTest {
    // 集成测试基类
      // 如果使用maven运行测试用例,需要在maven-surefire-plugin插件中将本基类排除执行,否则会报错,因为没有测试用例
}

然后,我们的测试基类继承该测试基类:

public class SystemInfoDaoTest extends BaseTest {
    // 获取系统数据源
      @Autowired
    private DataSource dataSource;
    @Autowired
    private PersonDao personDao;
    @Test
    public void getPersonCount() {
          // 构造一个连接到数据源的Request,此处可以先略过,后面会有详细介绍
        Request request = new Request(dataSource, "select count(1) from person where name = ?", "zhangsan");
          // assertj-db执行如上Request中的SQL,对获取的数据进行断言
        assertThat(request).row(0).column().value().isEqualTo(1);
    }
}


如果你不想使用SpringBoot的数据源,需要自定义数据源,那么可以在测试类中这么写:

public class SystemInfoDaoTest extends BaseTest {
    private Source dataSource;
    @Autowired
    private PersonDao personDao;
    private static final String DB_URL = "jdbc:postgresql://localhost:5432/mydb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true";
    private static final String DB_USER_NAME = "postgre";
    private static final String DB_PASSWORD = "postgre";
    @Before
    public void before(){
        this.dataSource = new Source(DB_URL,DB_USER_NAME,DB_PASSWORD);
    }
    @Test
    public void getUmCount() {
       // 构造一个连接到数据源的Request,此处可以先略过,后面会有详细介绍
        Request request = new Request(dataSource, "select count(1) from person where name = ?", "zhangsan");
          // assertj-db执行如上Request中的SQL,对获取的数据进行断言
        assertThat(request).row(0).column().value().isEqualTo(1);
    }
}

当然,还可以使用其它类型的DataSource,详细信息可以参考文末关于AssertJ-DB的官网内容。


Table

当数据源连接上之后,我们可以使用如下的语句来代表某一张具体的表:

Table table = new Table(dateSource, "person");

Request

一个Request可以代表一个即将要执行的SQL请求:

Request request = new Request(dataSource, "select count(1) from person where name = ?", "zhangsan");


Row

Row是基于上面table和request的结果的某一行数据:


// 取当前表的第二行数据
table.row(1);
// 取当前请求的第4行数据,然后再跳到第11行数据
request.row(3).row(10);


Column

Column是基于上面table和request的结果的某一列数据:

// 取当前表的第二列数据
table.column(1);
// 取当前请求的第4列数据,然后再跳到第11列数据
request.column(3).column(10);
// 取当前请求的第2行数据,然后取当前行的第4列单元格
request.row(1).column(3);


Value

Value是基于Row或者Column的某一单元格中的值:

// 取当前请求的第2行数据,然后取当前行的第4列单元格的值
request.row(1).column(3).value();

总结下来,只有DAO层的对数据库的增、删、改操作才需要使用AssertJ-DB,而查询操作是不需要的,因为查询已经将数据加载到内存中,只要使用AssertJ-Core做断言比较即可。


关于这些常用功能的详细案例,可以参考文末的Assertj-DB文档。


PS:


实验表明,对于事务回滚控制的测试用例,assertJ-DB似乎并不能得到我们想要的结果。


如下案例中,测试用例是事务回滚的,但是使用JdbcTemplate可以得到正确的结果,但是使用assertJ-DB就不行了。只能针对非事务回滚的测试用例,assertJ-DB才能得到正确的结果。这个目前还不知道怎么解决,暂时只能用JdbcTemplate替代。

    @Test
    public void addSystemInfoTest(){
        SystemUpdateDTO systemUpdateDTO = new SystemUpdateDTO();
        systemUpdateDTO.setSysNameCN("测试-商品管理系统");
        systemUpdateDTO.setSysNameEN("test-GMS");
        // 测试DAO逻辑-插入一条数据
        systemInfoDao.addSystemInfo(systemUpdateDTO);
        String querySql = "select count(1) from dw_sys_info dsi";
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        Integer rows = jdbcTemplate.queryForObject(querySql,Integer.class);
        // 1
        System.out.println("总共有" + rows);
        Request request = new Request(dataSource, querySql);
        // 0
        System.out.println("总共有" + request.getRow(0).getColumnValue(0).getValue());
    }


三、Mock技术


Mock框架有很多,古老的JMock、社区活跃的Mockito、还有我们今天要介绍的主角JMockit。


Mock技术是为了隔离被测试方法依赖的外部变量,从而可以使得测试方法的表现只受被测试方法本身的逻辑影响。举个例子:

@Service("personService")
public class PersonServiceImpl implements PersonService{
  @Autowired
  private InvokeService invokeService;
  @Override
  public Integer getPersonCountBySchool(String school){
    if(StringUtils.isEmpty(school)){
      return 0;
    }
    // 调用关联方获取数据的数量
    return invokeService.getPersonBySchool(school).size();
  }
  ...
}

我们如果想测试getPersonCountBySchool能否正常返回数据的数量,我们不必真的去执行invokeService.getPersonBySchool(school)调用关联方,只要使用Mock技术,让其返回我们设定的值即可:

public class PersonServiceImplTest extends BaseTest {
    @Tested
    @Autowired
    private PersonService personService;
    @Test
    public void testGetPersonCountBySchool(@Injectable InvokeService invokeService) {
        // 准备数据
        List<Person> personList = new ArrayList<>();
        Person peter = new Person("东方高中");
        Person jack = new Person("东方高中");
        personList.add(peter);
        personList.add(jack);
        // 模拟录制
        new Expectations(){
            {
                // 模拟调用关联方获取数据列表,无论入参是什么字符串,都返回上面准备好的列表
                invokeService.getPersonBySchool(anyString);
                result = personList;
            }
        };
        // 重放
        Integer personCount = personService.getPersonCountBySchool("华夏高中");
        // 验证
        assertThat(personCount).isEqualTo(2);
    }
}

在这里,最重要的两个注解就是@Tested和@Injectable,前者代表需要测试的类,后者代表需要mock的对象。


JMockit支持mock一个类、mock一个对象实例、mock一个对象中的某个具体的方法,甚至还可以对传入的参数进行检查,更多细节请参考文末列举的JMockit的官方文档。


相关文章
|
25天前
|
Java 测试技术 开发者
必学!Spring Boot 单元测试、Mock 与 TestContainer 的高效使用技巧
【10月更文挑战第18天】 在现代软件开发中,单元测试是保证代码质量的重要手段。Spring Boot提供了强大的测试支持,使得编写和运行测试变得更加简单和高效。本文将深入探讨Spring Boot的单元测试、Mock技术以及TestContainer的高效使用技巧,帮助开发者提升测试效率和代码质量。
136 2
|
1月前
|
XML Java 测试技术
【SpringBoot系列】初识Springboot并搭建测试环境
【SpringBoot系列】初识Springboot并搭建测试环境
73 0
|
3月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
1月前
|
安全 Java 数据库
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
这篇文章是关于Apache Shiro权限管理框架的详细学习指南,涵盖了Shiro的基本概念、认证与授权流程,并通过Spring Boot测试模块演示了Shiro在单应用环境下的使用,包括与IniRealm、JdbcRealm的集成以及自定义Realm的实现。
43 3
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
|
1月前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
125 0
|
2月前
|
JavaScript 前端开发 Java
Spring Boot+cucumber+契约测试
Spring Boot+cucumber+契约测试
21 0
Spring Boot+cucumber+契约测试
|
3月前
|
网络协议 Java API
SpringBoot整合Elasticsearch-Rest-Client、测试保存、复杂检索
这篇文章介绍了如何在SpringBoot中整合Elasticsearch-Rest-Client,并提供了保存数据和进行复杂检索的测试示例。
SpringBoot整合Elasticsearch-Rest-Client、测试保存、复杂检索
|
3月前
|
Java 测试技术
SpringBoot单元测试快速写法问题之区分链路环节是否应该被Mock如何解决
SpringBoot单元测试快速写法问题之区分链路环节是否应该被Mock如何解决
|
3月前
|
SQL Java 测试技术
SpringBoot单元测试快速写法问题之PorkService 接口中的 getPork 方法的作用如何解决
SpringBoot单元测试快速写法问题之PorkService 接口中的 getPork 方法的作用如何解决
|
3月前
|
测试技术
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
47 2