当前位置: 首页 > news >正文

【JUnit实战3_04】第二章:探索 JUnit 的核心功能(四)

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
作为第二章 JUnit 功能特性扫盲篇的最后一篇自学笔记,我个人对这部分的定位是增长见识、拓宽眼界为主。很多知识点都给人眼前一亮的感觉:原来 JUnit 5 还能这样用!但是时间精力确实有限,不可能完全展开讨论,留下关键印象就显得特别重要了。这里的关键诀窍是,记住典型案例的应用场景。

文章目录

    • 2.13 基于手动硬编码的字符串数组的参数化测试
    • 2.14 基于枚举类的参数化测试
    • 2.15 基于 CSV 文件的参数化测试
    • 2.16 动态测试
    • 2.17 Hamcrest 框架用法示例

2.13 基于手动硬编码的字符串数组的参数化测试

通过组合使用 @ParameterizedTest 注解和 @ValueSource 注解,可以自定义参数化测试用例的展示名称(默认将 strings 数组中的元素值作为各测试用例的子标题):

class ParameterizedWithValueSourceTest {private WordCounter wordCounter = new WordCounter();@ParameterizedTest@ValueSource(strings = {"Check three parameters", "JUnit in Action"})void testWordsInSentence(String sentence) {assertEquals(3, wordCounter.countWords(sentence));}
}

实测结果:

Fig2.7

2.14 基于枚举类的参数化测试

通过组合使用 @ParameterizedTest 注解和 @EnumSource 注解,可以自定义参数化测试用例的展示名称(能在枚举注解内设置筛选条件,这个特性让人眼前一亮):

import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;class ParameterizedWithEnumSourceTest {private WordCounter wordCounter = new WordCounter();@ParameterizedTest@EnumSource(Sentences.class)void testWordsInSentence(Sentences sentence) {assertEquals(3, wordCounter.countWords(sentence.value()));}@ParameterizedTest@EnumSource(value = Sentences.class, names = {"JUNIT_IN_ACTION", "THREE_PARAMETERS"})void testSelectedWordsInSentence(Sentences sentence) {assertEquals(3, wordCounter.countWords(sentence.value()));}@ParameterizedTest@EnumSource(value = Sentences.class, mode = EXCLUDE, names = {"THREE_PARAMETERS"})void testExcludedWordsInSentence(Sentences sentence) {assertEquals(3, wordCounter.countWords(sentence.value()));}enum Sentences {JUNIT_IN_ACTION("JUnit in Action"),SOME_PARAMETERS("Check some parameters"),THREE_PARAMETERS("Check three parameters");private final String sentence;Sentences(String sentence) {this.sentence = sentence;}public String value() {return sentence;}}
}

实测结果:

Fig2.8

2.15 基于 CSV 文件的参数化测试

最让人意外的是 JUnit 5 还能通过 @CsvFileSource 注解直接解析 CSV 文件,并将解析结果以 方法参数的形式 注入参数化测试(例如示例代码中的 expectedsentence)。如果数据量较小,也可以用 @CsvSource 注解手动输入 CSV 格式的数据源。

这部分内容其实和上一篇的参数注入有所重叠,为了强调 JUnit 在文件解析方面的强大,这里单列出来以便日后复盘。查看 @CsvFileSource 注解的源码还可以了解更多配置项,如分隔符的设置等,这里就不展开了。

示例代码1:

class ParameterizedWithCsvFileSourceTest {private WordCounter wordCounter = new WordCounter();@ParameterizedTest@CsvFileSource(resources = "/word_counter.csv")void testWordsInSentence(int expected, String sentence) {assertEquals(expected, wordCounter.countWords(sentence));}
}

上述代码中,CSV 文件路径是相对测试的 CLASSPATH 而言的,即 src/test/resources/。运行结果:

Fig2.9

示例代码2:(手动录入数据源)

class ParameterizedWithCsvSourceTest {private final WordCounter wordCounter = new WordCounter();@ParameterizedTest(name = "Line {index}: [{0}] - {1}")@CsvSource(value = {"2, Unit testing", "3, JUnit in Action", "4, Write solid Java code"})@DisplayName(value = "should parse CSV file")void testWordsInSentence(int expected, String sentence) {assertEquals(expected, wordCounter.countWords(sentence));}
}

运行结果:

Fig2.5

2.16 动态测试

利用工厂模式注解 @TestFactory 可以动态生成多个测试用例。需要注意的是,最核心的测试方法需要返回如下指定类型:

  • 一个 DynamicNode 型对象(DynamicNode 为抽象类,DynamicContainerDynamicTest 是其具体的实现类);
  • 一个 DynamicNode 型数组;
  • 一个基于 DynamicNodeStream 流;
  • 一个基于 DynamicNodeCollection 集合;
  • 一个基于 DynamicNodeIterable 可迭代对象;
  • 一个基于 DynamicNodeIterator 迭代器对象。

示例代码如下:

class DynamicTestsTest {private PositiveNumberPredicate predicate = new PositiveNumberPredicate();@BeforeAllstatic void setUpClass() {System.out.println("@BeforeAll method");}@AfterAllstatic void tearDownClass() {System.out.println("@AfterAll method");}@BeforeEachvoid setUp() {System.out.println("@BeforeEach method");}@AfterEachvoid tearDown() {System.out.println("@AfterEach method");}@TestFactoryIterator<DynamicTest> positiveNumberPredicateTestCases() {return asList(dynamicTest("negative number", () -> {System.out.println("negative number ...");assertFalse(predicate.check(-1));}),dynamicTest("zero", () -> {System.out.println("zero ...");assertFalse(predicate.check(0));}),dynamicTest("positive number", () -> {System.out.println("positive number ...");assertTrue(predicate.check(1));})).iterator();}
}

实测结果:

Fig2.10

可以看到,各生命周期注解仅对添加了 @TestFactory 工厂注解的外层方法本身生效,对工厂方法中动态生成的测试用例均无效。具体的测试用例行为,由动态测试的第二个参数,即传入的 Executable 型断言对象决定。

备忘:意外发现一个 IDEA 控制台输出的 Bug

实测发现,IntelliJ IDEA 中的控制台输出结果与期望的顺序不符,每次在 IDEA 中运行动态测试,@BeforeAll@AfterAll 注解的输出结果都在最末尾:

### snip ###
@BeforeEach method
negative number ...
zero ...
positive number ...
@AfterEach method
@BeforeAll method
@AfterAll methodProcess finished with exit code 0

但在命令行中的就是正确的顺序:

> mvn test -Dtest=DynamicTestsTest
### snip ###
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.manning.junitbook.ch02.dynamic.DynamicTestsTest
@BeforeAll method
@BeforeEach method
negative number ...
zero ...
positive number ...
@AfterEach method
@AfterAll method
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.016 s - in com.manning.junitbook.ch02.dynamic.DynamicTestsTest
### snip ###

2.17 Hamcrest 框架用法示例

Hamcrest 辅助框架提供了更具声明式风格的测试断言方法和组合工具,可以让代码可读性更好,同时报错信息的提示更加友好。其中会大量涉及 Matcher 对象的组合应用(matcher 又称为 约束(constraints判定条件(predicates,相关概念源自 JavaC++Objective-CPythonPHP 等编程语言)。

对函数式编程感兴趣的朋友不妨多看看 Hamcrest 的源码,学习学习当中定义的各种辅助 Matcher 是如何构建一个相对完善的测试语义的。毕竟 Java 8 的函数式特性已经发布十余年了,我本人也在各类项目中有意尝试这些写法,但在构筑流畅的语义抽象层时经常遭遇巨大阻力,以致于很多项目后期运维难以为继,可读性和命名上的一致性都不强。究其原因,一是自身的英语素养还有待加强,二是可供参考的体例不多,三是社区对函数式编程的响应积极性并不高,尤其是在绝大多数中小公司都将代码重构当成增加项目综合成本的一大来源,这方面的刻意练习就更少了。

要启用 Hamcrest 也不难,先新增必要的 Maven 依赖:

<dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-library</artifactId><version>2.1</version><scope>test</scope>
</dependency>

下面通过常规报错提示和 Hamcrest 提示的效果对比来演示具体用法(报错信息演示):

public class HamcrestListTest {private List<String> values;@BeforeEachpublic void setUp() {values = new ArrayList<>();values.add("John");values.add("Michael");values.add("Edwin");}@Test@DisplayName("List without Hamcrest will intentionally fail to show how failing information is displayed")public void testListWithoutHamcrest() {assertEquals(3, values.size());assertTrue(values.contains("Oliver") || values.contains("Jack") || values.contains("Harry"));}@Test@DisplayName("List with Hamcrest will intentionally fail to show how failing information is displayed")public void testListWithHamcrest() {assertThat(values, hasSize(3));assertThat(values, hasItem(anyOf(equalTo("Oliver"), equalTo("Jack"), equalTo("Harry"))));assertThat("The list doesn't contain all the expected objects, in order", values, contains("Oliver", "Jack", "Harry"));assertThat("The list doesn't contain all the expected objects", values, containsInAnyOrder("Jack", "Harry", "Oliver"));}
}

运行结果:

Fig2.11

常见的 Hamcrest 静态工厂方法:

工厂方法功能
anything匹配任意内容,常用于单纯提高可读性。
is仅用于提高语句的可读性
allof检查其中的条件是否都满足
anyOf检查包含的条件是否存在任意一个满足
not反转目标条件的语义
instanceOf检查某对象是否均为另一对象的实例
sameInstance测试对象的同一性
nullValuenotNullValue测试空值、非空值
hasProperty测试该 Java Bean 是否具有某个属性
hasEntryhasKeyhasValue测试目标 Map 是否包含指定的项、键或值
hasItemhasItems检测目标集合中是否存在某个或某些子项
closeTo
greaterThan
greaterThanOrEqualTo
lessThan
lessThanOrEqualTo
测试目标数值是否接近、
大于、
大于或等于、
小于、
小于或等于某个值
equalToIgnoringCase测试某字符串是否与另一字符串相等(忽略大小写)
equalToIgnoringWhiteSpace测试某字符串是否与另一字符串相等(忽略空白字符)
containsStringendsWithstartsWith测试某字符串是否包含指定字符串、或者以指定字符串开头或结尾

不难看到,这些声明式的工厂方法都是非常简洁有力的,几乎不需要额外的注释。或许这才是函数式编程的正确打开方式吧。

(第二章完)

http://www.dtcms.com/a/508113.html

相关文章:

  • 如何用ps做网站ui网站制作公司北京华网
  • 深入理解Lua闭包机制:从原理到mpv实战(深度!)
  • Flask【python】
  • day13_mvc 前后端分离
  • 网站定位方案威海信息网
  • 一个WEB端的API测试工具、API文档编写工具、定时任务调度工具
  • 电商秒杀系统设计 Java+MySQL实现高并发库存管理与订单处理
  • 中国建设银行笔试确认网站万网域名在中国电信网站备案系统
  • 个人网站 组建长沙旅游
  • 矩阵的奇异值分解(SVD)及其在计算机图形学中的应用
  • 青海旅游的网站建设公司网站可以免费建吗
  • 镇江网站建设zjmfkj厅网站建设中标公告
  • 高光谱成像用于草地可燃物含水率估测的研究进展
  • Product Hunt 每日热榜 | 2025-10-20
  • C++STL之unordered_map,unordered_set与哈希表
  • 电商税新规下的第三方支付云账户分账解决方案
  • 【Linux指南】冯诺依曼体系结构:现代计算机的基石
  • 建站之星怎么收费WordPress首页不收录
  • 酒店网站设计方案淘宝运营培训机构
  • sm2025 模拟赛24 (2025.10.20)
  • 【完整源码+数据集+部署教程】【后勤&运输集装箱】集装箱表面腐蚀检测系统源码&数据集全套:改进yolo11-swintransformer
  • 20.哈希
  • 公司网站表达的内容wordpress怎么上传高清图片大小
  • 慧聪网网站建设策略用腾讯云做淘宝客网站视频
  • XSS_and_Mysql_file靶场攻略
  • 引领交易革命:一站式去中心化交易所Swap开发一体化方案
  • 多样化的网站建设公司张雪峰谈工业设计专业
  • Houdini UV节点uvunwrap 和 uvproject和uvtexture 有什么区别
  • Vue3 条件语句详解
  • CAN总线: 位同步,接收方数据采样