《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(1)

简介: 《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(1)

无效单测:那些年,我们写过的无效单元测试

前言

 

那些年,为了学分,我们学会了面向过程编程;

那些年,为了就业,我们学会了面向对象编程;

那些年,为了生活,我们学会了面向工资编程;

那些年,为了升职加薪,我们学会了面向领导编程;

那些年,为了完成指标,我们学会了面向指标编程;

……

那些年,我们学会了敷衍地编程;

那些年,我们编程只是为了敷衍。

 

现在,领导要响应集团提高代码质量的号召,需要提升单元测试的代码覆盖率。当然,我们不能让领导失望,那就加班加点地补充单元测试用例,努力提高单元测试的代码覆盖率。至于单元测试用例的有效性,我们大抵是不用关心的,因为我们只是面向指标编程。

 

我曾经阅读过一个Java服务项目,单元测试的代码覆盖率非常高,但是通篇没有一个依赖方法验证(Mockito.verify)、满纸仅存几个数据对象断言(Assert.assertNotNull)。我说,这些都是无效的单元测试用例,根本起不到测试代码BUG和回归验证代码的作用。后来,在一个月黑风高的夜里,一个新增的方法调用,引起了一场血雨腥风。

 

编写单元测试用例的目的,并不是为了追求单元测试代码覆盖率,而是为了利用单元测试验证回归代码——试图找出代码中潜藏着的BUG。所以,我们应该具备工匠精神、怀着一颗敬畏心,编写出有效的单元测试用例。在这篇文章里,作者通过日常的单元测试实践,系统地总结出一套避免编写无效单元测试用例的方法和原则。

 

一、 单元测试简介

 

1. 单元测试概念

 

在维基百科中是这样描述的:

 

在计算机编程中,单元测试又称为模块测试,是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。

 

2. 单元测试案例

 

首先,通过一个简单的服务代码案例,让我们认识一下集成测试和单元测试。

 

1) 服务代码案例

 

这里,以用户服务(UserService)的分页查询用户(queryUser)为例说明。


image.png

2) 集成测试用例

 

很多人认为,凡是用到JUnit测试框架的测试用例都是单元测试用例,于是就写出了下面的集成测试用例。

 

image.png


集成测试用例主要有以下特点:

 

依赖外部环境和数据;

需要启动应用并初始化测试对象;

直接使用`@Autowired`注入测试对象;

有时候无法验证不确定的返回值,只能靠打印日志来人工核对。

 

3) 单元测试用例

 

采用JUnit+Mockito编写的单元测试用例如下:


@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    /** 定义静态常量 */
    /** 资源路径 */
    private static final String RESOURCE_PATH = "testUserService/";
    /** 模拟依赖对象 */
    /** 用户DAO */
    @Mock
    private UserDAO userDAO;
    /** 定义测试对象 */
    /** 用户服务 */
    @InjectMocks
    private UserService userService;
    /**
     * 测试: 查询用户-无数据
     */
    @Test
    public void testQueryUserWithoutData() {
        // 模拟依赖方法
        // 模拟依赖方法: userDAO.countByCompany
        Long companyId = 123L;
        Long startIndex = 90L;
        Integer pageSize = 10;
        Mockito.doReturn(0L).when(userDAO).countByCompany(companyId);
        // 调用测试方法
        String path = RESOURCE_PATH + "testQueryUserWithoutData/";
        PageDataVO<UserVO> pageData = userService.queryUser(companyId, startIndex, pageSize);
        String text = ResourceHelper.getResourceAsString(getClass(), path + "pageData.json");
        Assert.assertEquals("分页数据不一致", text, JSON.toJSONString(pageData));
        // 验证依赖方法
        // 验证依赖方法: userDAO.countByCompany
        Mockito.verify(userDAO).countByCompany(companyId);
        // 验证依赖对象
        Mockito.verifyNoMoreInteractions(userDAO);
    }
    /**
     * 测试: 查询用户-有数据
     */
    @Test
    public void testQueryUserWithData() {
        // 模拟依赖方法
        String path = RESOURCE_PATH + "testQueryUserWithData/";
        // 模拟依赖方法: userDAO.countByCompany
        Long companyId = 123L;
        Mockito.doReturn(91L).when(userDAO).countByCompany(companyId);
        // 模拟依赖方法: userDAO.queryByCompany
        Long startIndex = 90L;
        Integer pageSize = 10;
        String text = ResourceHelper.getResourceAsString(getClass(), path + "dataList.json");
        List<UserVO> dataList = JSON.parseArray(text, UserVO.class);
        Mockito.doReturn(dataList).when(userDAO).queryByCompany(companyId, startIndex, pageSize);
        // 调用测试方法
        PageDataVO<UserVO> pageData = userService.queryUser(companyId, startIndex, pageSize);
        text = ResourceHelper.getResourceAsString(getClass(), path + "pageData.json");
        Assert.assertEquals("分页数据不一致", text, JSON.toJSONString(pageData));
        // 验证依赖方法
        // 验证依赖方法: userDAO.countByCompany
        Mockito.verify(userDAO).countByCompany(companyId);
        // 验证依赖方法: userDAO.queryByCompany
        Mockito.verify(userDAO).queryByCompany(companyId, startIndex, pageSize);
        // 验证依赖对象
        Mockito.verifyNoMoreInteractions(userDAO);
    }
}

单元测试用例主要有以下特点:

 

不依赖外部环境和数据;

不需要启动应用和初始化对象;

需要用@Mock来初始化依赖对象,用@InjectMocks来初始化测试对象;

需要自己模拟依赖方法,指定什么参数返回什么值或异常;

因为测试方法返回值确定,可以直接用Assert相关方法进行断言;

可以验证依赖方法的调用次数和参数值,还可以验证依赖对象的方法调用是否验证完毕。

 



《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(2) https://developer.aliyun.com/article/1232114?groupCode=java




 

相关文章
|
6天前
|
Java 测试技术 数据库
【JAVA基础篇教学】第十七篇:Java单元测试
【JAVA基础篇教学】第十七篇:Java单元测试
|
5天前
|
Java 测试技术
Java一分钟之-单元测试:JUnit与TestNG
【5月更文挑战第16天】本文介绍了Java常用的单元测试框架JUnit和TestNG,JUnit以其简洁注解受到青睐,而TestNG则提供更高级功能如参数化测试。常见问题包括测试未执行、断言失败等,解决办法包括检查项目配置、调整测试顺序。注意保持测试简单独立,确保高覆盖率。选择合适的框架可提升代码质量。
11 0
|
6天前
|
监控 数据可视化 IDE
python自动化测试实战 —— 单元测试框架
python自动化测试实战 —— 单元测试框架
20 2
|
6天前
|
测试技术
测试基础 Junit单元测试框架
测试基础 Junit单元测试框架
15 2
测试基础 Junit单元测试框架
|
6天前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
18 1
|
6天前
|
Java 测试技术 Maven
Spring Boot单元测试报错java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]
Spring Boot单元测试报错java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]
|
6天前
|
监控 JavaScript 前端开发
【TypeScript技术专栏】TypeScript的单元测试与集成测试
【4月更文挑战第30天】本文讨论了在TypeScript项目中实施单元测试和集成测试的重要性。单元测试专注于验证单个函数、类或模块的行为,而集成测试关注不同组件的协作。选用合适的测试框架(如Jest、Mocha),配置测试环境,编写测试用例,并利用模拟和存根进行隔离是关键。集成测试则涉及组件间的交互,需定义测试范围,设置测试数据并解决可能出现的集成问题。将这些测试整合到CI/CD流程中,能确保代码质量和快速响应变化。
|
6天前
|
IDE 测试技术 持续交付
【专栏】利用Python自动化测试与单元测试框架提升代码质量与效率
【4月更文挑战第27天】本文探讨了Python自动化测试与单元测试框架在提升代码质量与效率中的作用。Selenium、Appium用于Web和移动应用自动化测试,pytest提供强大、易扩展的测试支持。unittest是Python标准的单元测试框架,支持结构化测试用例和丰富的断言。实践中,应制定测试计划,编写高质量测试用例,实行持续集成与测试,并充分利用测试报告。这些工具和策略能有效保障代码质量和提升开发效率。
|
6天前
|
测试技术 API 持续交付
【专栏】Python在自动化测试与单元测试中的应用,强调其简洁语法和丰富库的优势
【4月更文挑战第27天】本文探讨了Python在自动化测试与单元测试中的应用,强调其简洁语法和丰富库的优势。文章分为三部分:首先,阐述自动化测试的重要性及Python的易学性、库支持、跨平台和社区支持;其次,介绍了Python的Unittest标准测试框架和Pytest第三方框架的特点与用法;最后,讨论了Web UI和API自动化测试实践,并提出持续集成、测试金字塔等最佳实践。Python为软件开发的测试环节提供了强大支持,帮助构建更稳定的系统。
|
6天前
|
Java 测试技术 开发者
Java单元测试与集成测试:确保代码质量的最佳实践
【4月更文挑战第2天】在软件开发中,单元测试验证单个代码单元(如Java类或方法)的功能,确保其正确性;而集成测试则关注多个组件协作时的交互。JUnit是常见的Java单元测试框架,集成测试则检验组件间接口的兼容性。Spring框架提供了集成测试的支持。遵循良好编码习惯,编写可测试代码,设计全面的测试用例,是保证代码质量和稳定性的关键。