介绍

junit 5 是开发人员中众所周知的 java 测试框架/库。它是 junit 4 的演变,并带有许多很棒的功能。最重要的之一是设置前置条件和后置条件,通过术语“之前”(前置条件)和“之后”(后置条件)来了解。

它有 2 种支持的方式:Before/After All 和 Before/After Each。
“全部”部分意味着代码块可以在初始化所有测试之前或之后作为前置条件或后置条件执行。 “Each”部分意味着代码块可以作为每次测试之前或之后的前置条件或后置条件执行。

JUnit 5 官方文档对这些策略说了以下内容,即注释:

注释
描述

@BeforeEach
表示被注解的方法应该在当前类中的每个 @Test、@RepeatedTest、@ParameterizedTest 或 @TestFactory 方法之前执行;类似于 JUnit 4 的 @Before。这些方法是继承的,除非它们被重写

@AfterEach
表示被注解的方法应该在当前类中的每个 @Test、@RepeatedTest、@ParameterizedTest 或 @TestFactory 方法之后执行;类似于 JUnit 4 的 @After。此类方法将被继承,除非它们被重写。

@BeforeAll
表示被注解的方法应该在当前类中的所有@Test、@RepeatedTest、@ParameterizedTest、@TestFactory方法之前执行;类似于 JUnit 4 的 @BeforeClass。此类方法是继承的,除非它们被重写,并且必须是静态的,除非使用“每类”测试实例生命周期。

@毕竟
表示被注解的方法应该被执行毕竟当前类中的@Test、@RepeatedTest、@ParameterizedTest、@TestFactory方法;类似于 JUnit 4 的 @AfterClass。此类方法是继承的,除非它们被重写,并且必须是静态的,除非使用“每类”测试实例生命周期。

我们要解决什么问题
仅通过查看注释,我们就知道它涵盖了我们测试所需的大部分场景。
我们有 @BeforeAll 和 @AfterAll 注释作为一般前置或后置条件。

良好的测试架构中应用的一种常见测试模式是 BaseTest 类:我们可以在其中添加前置条件和后置条件,这些前置条件和后置条件将通过继承在不同的测试之间共享。通常,我们希望以不同的方式控制这些条件。开发者有不同的方式通过 BaseTest 模式来控制它:
任何测试开始时只打开浏览器一次,以节省时间
为不同的测试类保持打开的容器(Testcontainer)
在执行任何测试之前摄取数据,并在执行所有测试后将其删除

我有一个坏消息:JUnit 5 无法控制上述三个场景,因为 @BeforeAll 和 @AfterAll 是针对每个测试实例(即每个测试类)执行的。其他框架(例如 TestNG)具有称为 @BeforeSuite 和 @AfterSuite 的功能,而这正是我们想要的 JUnit 5 不支持的功能。

让我们了解如何使用 JUnit 5 来??实现此目的以及如何解决此问题。

BeforeAllCallback 和 AfterAllCallback 接口

你可能会像我无数次一样进行一些Google搜索,遇到BeforeAllCallback和AfterAllCallback接口,它们是测试生命周期回调的扩展。这似乎是一个很好的解决方案,因为这些接口使您能够运行 @BeforeAll 或 @AfterAll。

公共类 MyExtension 实现 BeforeAllCallback、AfterAllCallback {
@覆盖
公共无效 afterAll(ExtensionContext 上下文){
// 预一般条件
}

@覆盖
公共无效beforeAll(ExtensionContext上下文){
    //发布一般条件
}

}

登录后复制

UML 图显示了使用名为 MyExtension 的 JUnit 5 扩展的 BaseTest,该扩展实现了 BeforeAllCallback 和 AfterAllCallback。基础测试用于FeatureTest1 和FeatureTest2。请注意,MyExtension 有 beforeAll 和 afterAllMethods,BaseTest 也有。它解决了这个问题,因为 MyExtension 将充当全局之前和之后的角色,因为 BaseTest 中的扩展将在每个测试实例中运行,这意味着在 Feature1Test 和 Feature2Test 运行时运行。不幸的是,事实并非如此。如果我们在每个方法中仅添加 System.out.println() 调用,则输出将如下所示:

[信息] ---------------------------------------------------------- ---------
[信息] 测试
[信息] ---------------------------------------------------------- --------
[信息] 运行 com.eliasnogueira.feature1.Feature1Test
MyExtension.beforeAll
BaseTest.beforeAll
功能1测试.test1
功能1测试.测试2
功能1测试.测试3
基础测试.毕竟
我的扩展.毕竟
[信息] 测试运行:3,失败:0,错误:0,跳过:0,已用时间:0.018 秒 -- 在 com.eliasnogueira.feature1.Feature1Test 中

[信息] 运行 com.eliasnogueira.feature2.Feature2Test
MyExtension.beforeAll
BaseTest.beforeAll
功能2测试.test1
功能1测试.测试2
基础测试.毕竟
我的扩展.毕竟
[信息] 测试运行:2,失败:0,错误:0,跳过:0,已用时间:0.002 秒 -- 在 com.eliasnogueira.feature2.Feature2Test 中
您可以看到 MyExtension 中的方法针对每个测试类以及 BaseTest 运行。这意味着 JUnit 针对每个测试实例运行它。不幸的是,JUnit 5 没有针对整个测试执行的任何测试之前或之后的一般前提条件的解决方案。

登录后复制登录后复制
想看看它的实际效果吗?
– 克隆此存储库:git clone https://github.com/eliasnogueira/junit5-before-after-all
– 切换到无解分支
– 运行 mvn 测试

如何解决

隧道尽头有光明,并不困难。和大家一样,我谷歌了一下,在这个 Stackoverflow 线程上发现了一个有趣的解决方法:https://stackoverflow.com/questions/43282798/in-junit-5-how-to-run-code-before-all-test.

请注意,这是一种解决方法,可能在未来的 JUnit 版本中不起作用。

该解决方案基于使用 BeforeAllCallback 接口,并在并行测试运行时使用线程锁来解决一般前置条件,并使用 ExtensionContext.Store.CloseableResource 接口模拟后置条件的 JUnit 存储机制。别担心,我会分解实现。

这个例子

这是一个简单的方法,只是为了向您展示该方法的有效性。该示例显示了通用 BaseTest 和每个功能的 BaseTest,其中将创建一个扩展以提供执行通用前置条件和后置条件的能力。

扩展实施

实现可以分四步完成,最终的解决方案如下:

1 公共类SetupExtension 实现BeforeAllCallback, Store.CloseableResource {
2
3 private static final Lock LOCK = new ReentrantLock();
4 私有静态易失性布尔开始= false;
5
6 @覆盖
7 公共无效beforeAll(ExtensionContext上下文){
8 锁.lock();
9
10 尝试{
11 if (!开始) {
12开始=真;
13 System.out.println("[前置条件]应该只运行一次");
14 context.getRoot().getStore(Namespace.GLOBAL).put("后置条件的占位符", this);
15}
16}最后{
17 // 释放访问权限
18 锁.unlock();
19}
20}
21
22
23 @覆盖
24 公共无效关闭(){
25 System.out.println("[后置条件]应该只运行一次");
26}
27}

实现必要的接口登录后复制

第 1 行显示两个接口:BeforeAllCallback 重写 beforeAll() 方法,该方法将控制一般前置条件,ExtensionContext.Store.CloseableResource 重写 close() 方法,该方法将模仿一般后置条件。

控制beforeAll的单次执行

为了保证只执行一次必须应用一种策略:控制它已经开始,这样beforeAll()就不会再次执行。

第 8 行显示我们正在锁定线程。这是确保任何并行执行都是可能的所必需的。第 11 行检查该代码之前是否执行过。当第一次将布尔值启动设置为 true 时,确保它不会在后续运行中转到代码块。最后部分解锁了线程。

实施一般前提条件

一般前提条件的任何必要实现都应该放在 if 条件内部,就这么简单。我们可以在第 13 行看到这一点。

添加信号(存储)来模仿一般后置条件

这里模仿一般后置条件的方法是通过 Store。在 JUnit 5 中,我们可以在扩展中存储对象以供以后检索,这可以使用 getStore(context).put(key, value) 来完成,其中上下文是根或当前上下文,键、值是键和增加其存储价值。

第 14 行创建一个虚拟存储对象,供稍后在自动 close() 方法调用中使用。

实现一般后置条件

ExtensionContext.Store.CloseableResource 接口中的 close() 方法将在扩展生命周期结束时被调用 [参考]。这是程序退出之前执行任何代码的最后机会。这样我们就可以模拟一般的后置条件了。

代码示例

https://github.com/eliasnogueira/junit5-before-after-all 项目显示了基于本文解释的实现,与“示例”部分中的图表相匹配。

运行测试时,您将看到以下输出:

[信息] ---------------------------------------------------------- ---------
[信息] 测试
[信息] ---------------------------------------------------------- --------
[信息] 运行 com.eliasnogueira.feature1.Feature1Test
[前提条件] 应该只运行一次
BaseTest.beforeAll
BaseFeature1Test.beforeAll
功能1测试.test1
功能1测试.测试2
功能1测试.测试3
BaseFeature1Test.毕竟
基础测试.毕竟
[信息] 测试运行:3,失败:0,错误:0,跳过:0,已用时间:0.019 秒 -- 在 com.eliasnogueira.feature1.Feature1Test 中

[信息] 运行 com.eliasnogueira.feature2.Feature2Test
BaseTest.beforeAll
BaseFeature2Test.beforeAll
功能2测试.test1
功能1测试.测试2
BaseFeature2Test.毕竟
基础测试.毕竟
[信息] 测试运行:2,失败:0,错误:0,跳过:0,已用时间:0.002 秒 -- 在 com.eliasnogueira.feature2.Feature2Test 中
[后置条件] 应该只运行一次
请注意,一般前置条件是作为 [pre-condition] Should run only Once 的文本输出,一般后置条件是作为 [post-condition] Should run only Once 的输出。您可以分别在所有测试执行的开始和结束时看到它们。

登录后复制登录后复制使用这种简单的方法,您可以在代码中拥有一般的前置条件和后置条件。
测试愉快!

    以上就是如何在 JUnit 5 中模拟真实的 BeforeAll 和 AfterAll的详细内容,更多请关注php中文网其它相关文章!