打开网易新闻 查看更多图片

作者 | Otavio Santana

译者 | 王强

策划 | Tina

要点

  • 现代 Java 企业测试要求变得越来越多样化,底层技术也在快速发展。开发人员和质量工程师应该采用数据驱动的测试,而非遵循传统的“经验法则”。

  • 目前正在发展的两个最新 Java 规范可用来展示测试实践所需的变化,它们是 Jakarta Data 和 Jakarta NoSQL。

  • 开发人员应通过文档建立清晰全面的测试程序,指导项目贡献者采用统一的测试方法。

  • 开发人员和质量工程师应该拥抱一众高级库,例如 JUnit Jupiter 和 AssertJ。这些库可以简化测试流程、增强代码可读性并提高整体测试效率。

  • 工程师应使用基于容器的现代框架(例如 Testcontainers)来确保 Jakarta EE 应用程序的测试条件是一致且可重复的。

在动态的软件开发世界中,开源项目的重要性怎么强调也不为过。这些协作成果推动了创新,并为技术领域的质量和持续性建立了基准。很多长期项目,例如已开源发布二十多年的 Java,还有 Jakarta EE 规范及其实现,都体现了开源计划的影响。

这些项目提供了很多宝贵的见解和方法,可以很容易地应用于各种企业环境,充分体现了强大的文档和测试文化的重要性。

本文旨在深入探讨一些最新规范中所采用的一系列测试程序和工具,重点关注数据驱动的测试方法。通过研究这些方法,我们试图找出确保开源项目质量、可靠性和长寿的秘诀。我们将探索不断发展的软件开发领域中测试实践的复杂性,并探究如何利用这些原则来提高企业项目的质量。

我们将专注于目前正在发展的两个最新规范:Jakarta Data 和 Jakarta NoSQL。这些新兴规范代表了 Jakarta EE 生态系统中的创新工作,并有望重新定义 Java 企业级应用程序与数据的交互方式。

Jakarta Data 是 Jakarta EE 家族的最新成员之一,它定义了一组核心 API,旨在帮助开发人员构建 Jakarta EE 应用程序,并轻松访问各种数据技术。

此规范使开发人员能够利用关系和非关系数据库、基于云的数据服务和其他数据技术的优势。其目标是在 Jakarta EE 应用程序中培养一种更灵活、适应性更强的数据管理方法。

@Repository 注释是 Jakarta Data 的重要组成部分,因为它提供了存储库接口。此接口在你的域和数据库之间提供了简单的连接,允许你以域为中心的方式访问多个数据库。

图 1:Jakarta Data 表示。

同样,Jakarta NoSQL 代表了 Jakarta EE 演进的又一步。此规范和 Jakarta Data 为开发人员提供了一种将 Java 应用程序与 NoSQL 数据库集成的标准化方法。

我们的研究将围绕这两个规范及其实现,主要通过 Eclipse JNoSQL 的视角来展开。我们的目标是揭示它们如何为 Jakarta EE 项目中以数据为中心的测试方法铺平道路。通过探索数据驱动的测试原则,我们将深入解析那些确保 Jakarta EE 项目的质量、可靠性和寿命的复杂性。通过关注 Jakarta EE 的最新进展及其对测试方法的潜在影响,我们努力为开发人员和企业提供在不断发展的软件开发领域持续进步所需的知识和工具。

打开网易新闻 查看更多图片

图 2:Eclipse JNoSQL 插图,说明了使用这两个规范,我们可以在 NoSQL 数据库之间切换。

现在,我们已经为全面探索 Jakarta Data 和 Jakarta NoSQL 规范做好了准备,我们将深入研究这些规范如何改进 Jakarta EE 项目中的测试方法。我们将重点介绍作为我们实现平台的 Eclipse JNoSQL 。在下一节中,我们将开始一段对测试实践进行现代化的旅程,介绍应用于 Jakarta Data 和 Jakarta NoSQL 项目的具体步骤。

测试方法

在本节中,我们将探讨增强Jakarta EE 项目中测试方法的六个基本步骤,它们用来确保整个开发过程中的稳健性和可靠性。这些步骤旨在使 Jakarta Data 和 Jakarta NoSQL 项目与当代软件工程的需求保持一致:

  1. 测试指南:通过文档建立清晰全面的测试程序,指导项目贡献者采用统一的测试方法。

  2. 现代测试库:采用 JUnit Jupiter 和 AssertJ 等高级库来简化测试流程、增强代码可读性并提高整体测试效率。

  3. 数据驱动方法:采用数据驱动方法进行测试,探索各种数据集以发现 Jakarta EE 项目中的潜在问题和极端情况。

  4. 丰富的断言:利用各种断言库全面验证代码行为,确保 Jakarta EE 应用程序符合严格的质量标准。

  5. 扩展覆盖范围:使用 PITest、JaCoCo 或 Cobertura 等工具扩大测试覆盖范围。这将使 Jakarta EE 项目开发人员能够评估其测试的有效性并找出需要改进的领域。

  6. 用于测试的容器:利用容器创建和管理隔离的测试环境,确保 Jakarta EE 应用程序的测试条件是一致且可重复的。

采用这六个步骤后,开发人员可以改进他们在 Jakarta EE 项目中的测试实践,确保他们的应用程序在不断变化的软件需求面前保持稳健性、可靠性和弹性。

下面让我们深入研究每个步骤,并揭示它们如何帮助 Jakarta EE 项目取得成功。

图 3:在 Jakarta EE 项目中增强测试方法的步骤。

在测试的第一步中,起点是文档:它是充当罗盘的一份测试指南,引导所有项目贡献者朝着质量和可靠性的共同愿景前进。它封装了一组最佳实践和方法,用于管理测试程序来确保整个项目的一致性和有效性。

在 Jakarta JNoSQL 和 Jakarta Data 项目中,这些测试指南塑造了项目的测试文化,并构建了对质量的承诺。它们的内容包括:

  • 要使用的工具:定义的测试集和相关工具,以简化测试流程并确保贡献之间的一致性。

  • 命名约定:测试类、方法、描述和结构的命名约定一致性可增强测试的可读性和可维护性。

  • 具体的测试创建方法:测试创建方法指南(例如软断言和扩展断言)可确保对代码行为的验证。

  • 扩展覆盖范围:强调扩展测试覆盖范围,包括突变测试等技术、发现隐藏的错误和提高整体代码质量。

这些指南可作为测试的路线图,并强调了文档的重要性。它们为项目贡献者提供了明确的起点,便于无缝集成到测试框架中。例如,Jakarta Data 中的文档(如本文提供的 Asciidoc 格式的示例所示)概述了要使用的框架、命名约定、测试结构等,以设定项目内测试实践的标准。

要进一步探索,请参阅 GitHub 上 Jakarta Data 项目的测试指南。

作为对该指南的补充,我们提供了以下技巧:

  • 使用工具:推荐的工具包括 JUnit 5 与 AssertJ 的结合,提供具有增强断言功能的测试框架。

  • 命名约定:测试类应以测试主题命名,后跟“Test”后缀,以提升测试套件内的清晰度和组织性。

  • 测试方法描述:测试方法应使用结构化方法描述测试意图:

    • 前缀“should”

    • Action 名称

  • 为快乐路径提供可选预期结果,为负面场景(极端情况)提供强制性结果

例如,下面的代码片段演示了如何使用指南创建测试:

class CalculatorTest {
@Test
@DisplayName("Should sum up correctly two numbers")
void sumUpCorrectly() {
// AAA pattern (arrange, act, assert)
}
}

首选软断言:建议使用软断言,而不是硬断言。它们允许在单个测试方法中执行多个语句,而不会在第一次失败时中止测试执行。这样可以对测试场景进行更全面的验证。

assertSoftly(softly->{
softly.assertThat(pageable.size()).isEqualTo(20);
softly.assertThat(pageable.page()).isEqualTo(1L);
softly.assertThat(pageable.mode()).isEqualTo(Pageable.Mode.CURSOR_NEXT);
softly.assertThat(pageable.cursor().size()).isEqualTo(3);
softly.assertThat(pageable.cursor().getKeysetElement(0)).isEqualTo("First");
softly.assertThat(pageable.cursor().getKeysetElement(1)).isEqualTo(2L);
softly.assertThat(pageable.cursor().getKeysetElement(2)).isEqualTo(3);
});

对 Jakarta EE 项目中的测试方法进行现代化的第二步,主要是精心选择和采用现代化测试工具和库。虽然 Jakarta EE 的根源可以追溯到 1999 年诞生的 Java EE 平台,但现代化工具对于在当今的软件领域保持敏捷性和竞争力来说是至关重要的。Jakarta NoSQL 和 Jakarta Data 是 Jakarta EE 中最年轻的规范之二,它们无缝集成了一些现代测试库,以增强代码的可维护性、可读性和测试覆盖率。在这些库中,JUnit 5 的采用非常亮眼,它提供了可显著改善测试实践的一些高级特性。

事实证明,迁移到 JUnit 5(特别是 JUnit Jupiter)是值得的,它为 Jakarta EE 项目带来了许多有价值的特性。这些特性包括带来更清晰测试意图的测试描述、用于处理特定于环境的测试场景的环境条件,以及用于数据驱动测试的各种来源,例如值来源、方法来源和参数来源。

通过利用 JUnit 5 的上述特性,开发人员可以改进 Jakarta EE 项目的测试实践,以确保在不断变化的软件需求面前保持稳健性、可靠性和适应性。我们来深入研究如何利用这些特性来增强 Jakarta EE 项目中的测试。

对 Jakarta EE 项目中的测试方法进行现代化的第三步是采用数据驱动测试。本质上,这种方法是使用不同的数据集迭代相同的测试代码,从而提供一种快速增强代码覆盖率和可维护性的机制。

JUnit Jupiter 是 Jakarta EE 项目中的强大伙伴,主要用在 Jakarta Data 和 Jakarta NoSQL 中;它提供了 @ParameterizedTest 注释来简化数据驱动测试。此注释和多种来源选项简化了将动态数据注入测试用例的过程。

在 ValueWriterDecoratorTest 的第一个示例代码片段中,@ValueSource 注释用于将参数中的值注入测试方法。它能轻松测试不同类之间的兼容性。

@ParameterizedTest(name = "must be compatible to {0}")
@DisplayName("Should be able to verify the test compatibility")
@ValueSource(classes = {Enum.class, Optional.class, Temporal.class})
@SuppressWarnings("unchecked")
void shouldVerifyCompatibility(Class supportedClass) {
assertThat(valueWriter.test(supportedClass)).isTrue();
}

如图 4 所示,我们可以看到单个测试基于 @ValueSource 和不同值执行了多次。

图 4:测试应用程序执行ValueWriterDecoratorTest类中定义的 shouldVerifyCompatibility() 方法。

此外,JUnit Jupiter 提供了更多高级选项来动态注入参数,如 TemporalWriterTest 中所示。此处,@MethodSource 注释从一个方法处请求值,从而使用各种输入来测试 temporal 转换。

@ParameterizedTest(name = "must convert {0}")
@DisplayName("Should be able to convert temporal")
@MethodSource("temporalDataForConversion")
void shouldConvert(Temporal temporal) {
String result = valueWriter.write(temporal);
assertThat(result).isEqualTo(temporal.toString());
}
static Stream temporalDataForConversion() {
return Stream.of(
arguments(LocalDateTime.now()),
arguments(LocalDate.now()),
arguments(LocalTime.now()),
arguments(Year.now()),
arguments(YearMonth.now()),
arguments(ZonedDateTime.now())
);
}

JUnit Jupiter 的优点在于其灵活性,允许开发人员将测试与特定数据值分离。它能够与外部数据源(如 Data Faker、数据库或 Web 服务)无缝集成,而不会影响测试套件的完整性。通过利用 JUnit Jupiter 提供的数据驱动测试方法,Jakarta EE 项目可以实现更高的测试覆盖率和可维护性,同时确保其软件解决方案的稳健性和可靠性。在对 Jakarta EE 项目中的测试方法进行现代化的过程中,第四步是利用 AssertJ 等库提供的大量断言。这些库通过许多断言和特性丰富了测试工具包,包括用于简化和增强验证过程的软断言方法。

AssertJ 以其自带的一个流畅的 API 库而闻名,它扩展了可用断言的范围并显著提高了代码的可读性。这种增强的可读性在 KeyValueQueryParserTest 等测试中得到了体现,其中 AssertJ 的流畅 API 简化了验证过程。

@Test
@DisplayName("Should execute prepared statement using 'put' setting a value")
void shouldExecutePrepareStatement2() {
// code ignored
assertSoftly(softly -> {
softly.assertThat(entity.key()).as("key is equal").isEqualTo("Diana");
softly.assertThat(entity.value()).as("entity is equal").isEqualTo("Hunt");
softly.assertThat(duration).as("duration is equal").isEqualTo(ofSeconds(10L));
});
}

在此示例中,AssertJ 软断言方法中的 assertSoftly() 方法允许在单个测试方法中执行多个断言,而不会在遇到第一次失败时中止测试执行。它带来了对测试场景的更全面验证,同时保持了测试代码的可读性和清晰度。通过采用 AssertJ 等库提供的大量断言,Jakarta EE 项目可以增强其测试套件的稳健性、可靠性和可维护性,最终确保其软件解决方案的质量和完整性。

对 Jakarta EE 项目中的测试方法进行现代化的第五步,我们转向突变测试。这种先进的技术使我们能够发现隐藏的错误并评估测试的强度。

PITest 是一种为 Java 和 JVM 设计的最先进的突变测试系统,在此过程中发挥着关键作用。PiTest 自动将故障或突变引入我们的代码库,使我们能够评估我们的测试对这些变化的响应程度。

突变测试的工作机制如下:将故障注入代码,然后执行我们的测试。如果测试失败,表明它检测到了突变,则认为突变已被终止。相反,如果测试通过,意味着它未能检测到突变,则突变仍可存活。

在 Eclipse JNoSQL 项目中,我们可以使用一个简单的命令将 PiTest 无缝集成到我们的测试工作流程中:

mvn clean test-compile -P pitest

此命令触发分析过程,生成全面的报告,提供有关测试覆盖率、变异分数和测试强度的见解。这些报告可通过位于 target/pit-reports 的 文件访问。

打开网易新闻 查看更多图片

图 5:针对Eclipse JNoSQL 通信核心项目的 PITest 执行。

上图 5 显示了 PiTest 的实际运行情况,展示了覆盖率、变异和测试强度指标。这种直观的表示使我们能够评估测试的有效性并找出改进领域。

通过 PITest 和突变测试,Jakarta EE 项目可以扩展其测试覆盖范围,检测潜在错误,并提高其软件解决方案的整体质量和可靠性。借助 PITest,我们可以确保我们的测试稳健而全面,从而对我们应用程序的稳定性充满信心。

对于 Jakarta Data 和 Jakarta NoSQL 项目,改进测试方法的最后一步是利用容器进行测试。由于核心数据库的集成情况,这一步至关重要,因为确保无缝交互和兼容性是必要的。

对 Jakarta EE 项目中的测试方法进行现代化的第六步也是最后一步中,Testcontainers 会用作一个强大的测试库。Testcontainers 提供简单轻量级的 API,通过封装在 Docker 容器中的精确服务简化了集成测试的引导工作。其功能可以管理测试期间数据库的生命周期,以保证测试开始时的初始化、执行期间的持久性和之后的正确拆卸。

在 JNoSQL-Database 项目中,多个集成需要与数据库直接集成,因此测试对合约的确认是非常重要的。在这里,我们见证了 Testcontainers 的实际操作,它无缝地协调数据库容器的设置和拆卸,以促进全面测试。

为了优化资源使用,该项目尽可能采用单例模式实例,尽可能减少初始化数据库实例的开销。这里使用了两种主要样式:管理容器的单例模式和基本测试模式。该项目正在向源自 Testcontainers 文档 的标准化结构过渡,以确保测试管理的一致性和清晰度。

abstract class AbstractContainerBaseTest {
static final MySQLContainer MY_SQL_CONTAINER;
static {
MY_SQL_CONTAINER = new MySQLContainer();
MY_SQL_CONTAINER.start();
}
}
class FirstTest extends AbstractContainerBaseTest {
@Test
void someTestMethod() {
String url = MY_SQL_CONTAINER.getJdbcUrl();
// Create a connection and run test as normal
}
}

为了提高测试执行的灵活性,开发人员利用多个属性,根据系统属性有选择地启用或禁用集成测试。可以根据需要切换重量级集成测试。

@EnabledIfSystemProperty(named = "jnosql.test.integration", matches = "true")
class MongoDBDocumentManagerTest {
// Executes test with a container
}

这种方法使开发人员能够有效地管理测试执行,有选择地运行资源密集型测试,从而优化 Jakarta EE 项目中的测试流程。通过利用容器进行测试,Jakarta Data 和 Jakarta NoSQL 项目可以验证数据库集成,从而提高其软件解决方案的可靠性和稳健性。利用容器在 Jakarta EE 项目,特别是在 Jakarta Data 和 Jakarta NoSQL 项目中进行测试,对于确保软件解决方案的稳健性和可靠性至关重要。Testcontainers 具有轻量级 API 和无缝 Docker 容器集成,有助于全面测试数据库交互。将 Testcontainers 与已建立的测试模式和实践(例如 Singleton 实例和灵活的测试执行切换)并用后,Jakarta EE 开发人员可以简化测试流程并验证数据库集成。这种细致的方法不仅提高了软件解决方案的质量和可靠性,还使开发人员能够自信地应对 Jakarta EE 生态系统中数据库集成的复杂性。

总 结

下面总结我们对 Jakarta EE 项目中测试方法进行现代化的探索结果,很明显,提高代码质量和可靠性是一项持续不断的努力。采用一系列创新工具和实践,例如数据驱动测试、广泛的断言和基于容器的测试,标志着行业在确保软件解决方案的稳健性方面取得了重大进展。

重要的是要认识到,与任何开源计划一样,Jakarta Data 和 Jakarta NoSQL 项目仍然充满活力并不断寻求改进。贡献者、测试人员和用户的积极参与对于推动这些项目向前发展、改进测试方法和提高 Jakarta EE 应用程序的可靠性是非常关键的。

对于那些寻求软件测试进一步见解和指导的人们来说,有很多参考资料和资源可用。一个值得推荐的是 Maurizio Aniche 的《有效软件测试:开发人员指南》,这是一本内容全面的著作,改变了许多人的测试实践。

此外,著名质量工程师和 Java Champion Elias Nogueira 等人的指导和领导在促进 Jakarta EE 项目中的良好测试实践方面发挥着关键作用。Nogueira 的博客和在 Twitter 等社交媒体平台上的活跃表现,为寻求提升对测试的理解并为推进 Jakarta EE 项目做出贡献的开发人员提供了宝贵的资源。

在我们探索不断发展的软件开发领域时,让我们继续致力于在 Jakarta EE 项目中培育质量、协作和持续改进的文化。我们可以共同赋能开发人员,加强测试实践,并提供无与伦比的可靠性和卓越性软件解决方案。

作者介绍

Otavio是一位屡获殊荣的软件工程师和架构师,他热衷于为其他工程师提供开源最佳实践,以构建高度可扩展且高效的软件。他是 Java 和开源生态系统的知名贡献者,并因其工作获得了无数奖项和荣誉。Otavio 的兴趣包括历史、经济、旅行,并且精通多种语言,还非常幽默。

Modernizing Testing Practices for Jakarta EE Projects (https://www.infoq.com/articles/jakartaee-testing-deep-dive/)

声明:本文为 InfoQ 翻译,未经许可禁止转载。