从测试金字塔(单元测试——功能测试——系统测试)的角度触发,测试工程师在进行测试设计和测试执行习惯性侧重于功能测试和系统测试,而把单元测试交由开发人员自己维护。一方面是因为时间和人力资源有限,测试人员不可能对单元测试、功能测试和系统测试全部都投入精力;另一方面是因为从代码开发能力而言,测试人员若想涉入单元测试,需要一定时间的学习成本。但是,是不是就意味着我们测试人员不需要或者不应该接触单元测试呢?答案当然是否定的。接触单元测试,不仅可以提高我们的编码能力,还能让我们更清楚开发代码的内部逻辑,对于功能测试和系统测试有很大的帮助。举个栗子:如果我们编写单元测试发现某个输入值导致程序异常,可以反推思考,对于整个系统来说,是否存在某个外部输入值经过程序运行后,触发该部分代码异常?
那么,从测试人员角度,我们要如何入手探索单元测试呢?以实现java代码的单元测试为例,我们从零开始,探索下junit5这个单元测试框架。
向来,从问题出发,能够帮助我们更好地切入了解一个事物。开始之前,让我们简单思考两个问题:1.我是否能像编写接口自动化测试、功能自动化测试一样,为单元测试打标签呢,根据不同的标签执行单元测试用例?2.我是否能像robotframework框架一样,进行测试用例套件层或测试用例层的一些初始化工作呢?例如:环境准备工作,清理环境工作。
1. 搭建junit5环境
JUnit 5
= JUnit Platform + JUnit Jupiter + JUnit Vintage,Junit Platform是在JVM上启动测试框架的基础,定义了TestEngine用于开发在平台上运行的测试框架的API;JUnit Jupiter是用于在JUnit 5中编写测试和扩展的新编程模型和 扩展模型的组合;JUnit Vintage提供了一个TestEngine用于在平台上运行基于JUnit 3和JUnit 4的测试,要求JUnit 4.12或更高版本出现在类路径或模块路径中。
详情可以参考https://junit.org/junit5/docs/current/user-guide/。简单阐述下maven工具对于junit5的配置方法:
2. 单元测试用例注释
回顾下我们的两个问题:1.如何给单元测试用例打标签,筛选标签运行用例?2.如何进行一些用例套件或用例层的初始化工作?这就需要说到junit5的junit jupiter组件的注释功能(引入jupiter组件,在java文件中import
org.junit.jupiter.api.*即可),jupiter提供的@Tag注释功能可以为用例标注不同的标签。简单示例如下图所示:
在上图的用例中,@Test表示方法是测试方法,在mvn test阶段会执行该方法;@Tag(“regression”)表示该单元测试标签为regression,使用mvn test –Dgroups=regression命令,可以筛选标签为regression的所有单元测试用例;@BeforeAll注释功能表示在所有用例前运行,类似在用例套件层执行初始化工作;@AferAll注释功能表示在所有用例后运行,类似在用例套件层执行清理工作。
jupiter提供的其他注释方法如下所示:
注解 | 描述 |
@Test
| 表示方法是测试方法。 |
@ParameterizedTest
| 表示方法是参数化测试。除非它们被覆盖, 否则这些方法会被继承。 |
@RepeatedTest
| 表示方法是重复测试的测试模板。除非它们被覆盖, 否则这些方法会被继承。 |
@TestFactory
| 表示方法是动态测试的测试工厂。除非它们被覆盖, 否则这些方法会被继承。 |
@TestTemplate
| 表示方法是测试用例的模板,旨在根据注册提供者返回的调用 上下文的数量多次调用。除非它们被覆盖,否则这些方法会被继承。 |
@TestClassOrder
| 用于为带注释的测试类中的测试类配置测试类执行顺序。 @Nested 此类注释是继承的。
|
@TestMethodOrder
| 用于配置带注解的测试类的测试方法执行顺序。 |
@TestInstance
| 用于为带注释的测试类配置测试实例生命周期。此类注释是继承的。 |
@DisplayName
| 声明测试类或测试方法的自定义显示名称。此类注释不会被继承。 |
@DisplayNameGeneration
| 声明测试类的自定义显示名称生成器。此类注释是继承的。 |
@BeforeEach
| 表示被注解的方法应该在当前类中的每个, , ,或方法之前执行; @Test@RepeatedTest@ParameterizedTest@TestFactory@Before |
@AfterEach
| 表示被注解的方法应该在当前类中的每个, , ,或方法之后执行; @Test@RepeatedTest@ParameterizedTest@TestFactory@After
|
@BeforeAll
| 表示被注解的方法应该在当前类的所有, , ,方法之前执行; @Test@RepeatedTest@ParameterizedTest@TestFactory@BeforeClassstatic
|
@AfterAll
| 表示注解的方法应该在当前类中的所有、、、方法之后执行; @Test@RepeatedTest@ParameterizedTest@TestFactory@AfterClassstatic |
@Nested
| 表示带注释的类是非静态嵌套测试类。除非使用“per-class”测试实例生命周期 @BeforeAll ,否则@AfterAll 方法不能直接在测试类中使用。
此类注释不会被继承。 |
@Tag
| 用于在类或方法级别声明。 |
@Disabled
| 用于禁用测试类或测试方法。 |
@Timeout
| 如果测试、测试工厂、测试模板或生命周期方法的执行超过给定持续时间, 则用于使测试、测试工厂、测试模板或生命周期方法失败。此类注释是继承的。 |
@ExtendWith
| 用于以声明方式注册扩展。此类注释是继承的。 |
@RegisterExtension
| 用于通过字段以编程方式注册扩展。除非它们被遮蔽,否则这些字段将被继承。 |
@TempDir
| 用于在生命周期方法或测试方法中通过字段注入或参数注入提供临时目录; 位于org.junit.jupiter.api.io 包装内。 |
3. 运行单元测试
用例编写完成后,就到了执行用例的部分。我们最后再来思考几个问题:
l 如何通过命令行筛选指定标签的用例运行?
使用-Dgroups参数筛选指定标签,如:mvn test
–Dgroups=regression,可以筛选标签为regression的用例运行。
l 通过命令行执行用例如何不通过标签筛选用例,而指定运行某个类中的方法呢?
可以通过-Dtest参数实现。如本文样例中,筛选@Tag(“regression”)的单元测试用例,也可以通过mvn test -Dtest=test#createOrcTable实现;
l 如何在通过命令行执行用例时,传入某些参数运行呢?
可以通过-D{参数名}实现。如本文样例中,定义了传入参数ch_server,可通过mvn test
–Dch_server=”10.10.10.10”的方式传入参数;
l 如何在通过命令行执行用例时,忽略失败的用例继续执行?
可以通过-Dmaven.test.failure.ignore=true实现。
最后,总结一下。本文只是借助几个测试人员在编写和执行功能自动化测试用例常见的几个问题初探junit5框架的单元测试用例编写和执行方法。更深入的内容,请待以后文章。