首页 > 技术文章 > 编写单元测试的几种方式

prepared 2021-06-16 09:31 原文

1 测试理论

ThoughtWorks 精益测试:

根据“二八原则”,80%的业务优先级可能只在其中20%的功能模块上,而其他80%的功能模块只占有20%的业务。

有些公司会追求100%的测试覆盖率,thoughtWorks 认为这是完全没有必要的,根据二八原则,花费大量时间追求 80% 非核心业务的完全测试覆盖,会造成大量浪费。

精益测试可以定义为:以业务价值为目标,以尽量少的成本交付高质量的软件,测试测在能体现价值的点上,做到有效覆盖,减少浪费。

测试分层-来源于thoughtWorkds博客

2 三种编写测试用例的方法

先创建数据库表,插入基础数据。

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);

DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

创建实体类、mapper类、service类、controller类,在源码中查看(源码地址在文末)

2.1 方式一——@Resource注入

不推荐使用该方式

这种方式,测试每个类都需要启动spring上下文,耗时很长,本身需要测试的方法耗时也就几毫秒,但是通过这种方式测试需要几秒到几分钟之间(根据项目大小,启动的bean数量不同)


import com.prepared.SpringBootUnitTest.entity.User;
import com.prepared.SpringBootUnitTest.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import javax.annotation.Resource;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 不建议 使用该方法
 *
 * 需要启动整个Spring上下文,耗时比较长
 *
 * @Author: prepared
 * @Date: 2021/6/8 10:50
 */
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest
public class UserMapperTest {

    @Resource
    UserMapper userMapper;

    @Test
    public void testLoad() {
        User user = userMapper.load(1);
        assertThat(user.getName()).isNotNull();
    }
}

2.2 方式二——按需加载

测试持久层推荐使用这种方式

按照需要加载对应的mapper文件,耗时短,速度快,实现简单。

PS: Mybatis3Utils.java 可以在文末的源码中找到。


import com.prepared.SpringBootUnitTest.entity.User;
import com.prepared.SpringBootUnitTest.mapper.UserMapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 建议使用,按需加载对应的Mapper文件
 *
 * @Author: prepared
 * @Date: 2021/6/8 10:50
 */
public class UserMapperTest {

    static SqlSession sqlSession;

    static UserMapper userMapper;

    @Before
    public void before() {
        sqlSession = Mybatis3Utils.getCurrentSqlSession();
        userMapper = sqlSession.getMapper(UserMapper.class);
    }

    @After
    public void after() {
        Mybatis3Utils.closeCurrentSession();
    }

    @Test
    public void testLoad() {
        User user = userMapper.load(1);
        assertThat(user.getName()).isNotNull();
    }
}

2.3 方式三——Mock模拟依赖类

测试service层、COLA框架的app层、controller层 推荐使用该方式

模拟依赖项,只测试需要测试的方法,速度快。

  1. 需要通过构造方法注入依赖
  2. 然后通过 Mockito 模拟需要注入的依赖项

Mockito.when(userMapper.insert(user)).thenReturn(1) 可以模拟调用依赖项调用方法的返回,可以模拟参数、任意参数返回等等,非常灵活方便。

任意参数例子:

Mockito.when(userMapper.insert(Mockito.any(User.class))).thenReturn(true);

import com.prepared.SpringBootUnitTest.common.ReturnT;
import com.prepared.SpringBootUnitTest.entity.User;
import com.prepared.SpringBootUnitTest.mapper.UserMapper;
import com.prepared.SpringBootUnitTest.service.UserService;
import com.prepared.SpringBootUnitTest.service.UserServiceImpl;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 建议使用
 *
 * @Author: prepared
 * @Date: 2021/6/8 11:03
 */
public class UserServiceUseCaseTest {

    private UserMapper userMapper = Mockito.mock(UserMapper.class);

    private UserService userService;

    @Before
    public void initUseCase() {
        userService = new UserServiceImpl(userMapper);
    }

    @Test
    public void testLoad() {
        User user = new User();
        user.setId(6L);
        user.setName("Angela");
        // 模拟userMapper返回
        Mockito.when(userMapper.insert(user)).thenReturn(1);
        ReturnT returnT = userService.insert(user);
        assertThat(returnT.getData()).isEqualTo(1);
    }
}

也可以通过注解实现

@Mock注解和@RunWith(SpringRunner.class)需要一起使用。

  1. 需要通过构造方法注入依赖
  2. 然后通过 Mockito 模拟需要注入的依赖项

import com.prepared.SpringBootUnitTest.common.ReturnT;
import com.prepared.SpringBootUnitTest.entity.User;
import com.prepared.SpringBootUnitTest.mapper.UserMapper;
import com.prepared.SpringBootUnitTest.service.UserService;
import com.prepared.SpringBootUnitTest.service.UserServiceImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.test.context.junit4.SpringRunner;

import javax.swing.*;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 建议使用
 *
 * @Author: prepared
 * @Date: 2021/6/8 11:03
 */
@RunWith(SpringRunner.class)
public class UserServiceUseCase2Test {

    @Mock
    private UserMapper userMapper;

    private UserService userService;

    @Before
    public void initUseCase() {
        userService = new UserServiceImpl(userMapper);
    }

    @Test
    public void testLoad() {
        User user = new User();
        user.setId(6L);
        user.setName("Angela");
        // 模拟userMapper返回
        Mockito.when(userMapper.insert(user)).thenReturn(1);
        ReturnT returnT = userService.insert(user);
        assertThat(returnT.getData()).isEqualTo(1);
    }
}

源码地址:https://github.com/prepared48/SpringBootUnitTest.git

更详细的介绍,可以参考:

1、[翻译]使用Spring Boot进行单元测试

2、thoughtWorks公司的博客,精益测试

推荐阅读