API与单元测试自动化

news/2025/12/2 10:45:59/文章来源:https://www.cnblogs.com/agony-wxl/p/19296357

概述

在现代软件开发中,API和单元测试自动化是确保代码质量的关键环节。Java凭借其强大的生态系统、丰富的测试框架和工具链,在测试自动化领域具有显著优势。

Java测试生态系统的优势

1. 丰富的测试框架

  • JUnit 5 - 现代测试框架
  • TestNG - 功能丰富的测试框架
  • Mockito - 强大的Mocking框架
  • RestAssured - API测试专用库
  • WireMock - HTTP Mock服务器

2. 构建工具集成

  • Maven - 依赖管理和测试生命周期
  • Gradle - 灵活的构建脚本
  • Surefire/Failsafe - 测试执行插件

3. 持续集成支持

  • Jenkins集成
  • GitLab CI管道
  • GitHub Actions工作流

JUnit 5 单元测试自动化实践

JUnit 5 是 Java 生态中最流行的单元测试框架,由三个主要模块组成:

  • JUnit Platform - 测试执行的基础平台
  • JUnit Jupiter - 新的编程模型和扩展模型
  • JUnit Vintage - 兼容 JUnit 3/4 的测试引擎

2. 环境配置

Maven 依赖

<properties><junit.jupiter.version>5.14.1</junit.jupiter.version><junit.platform.version>1.14.1</junit.platform.version>
</properties><!--region    Junit5    --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>${junit.jupiter.version}</version></dependency><!-- JUnit 5 API(编写测试用例) --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>${junit.jupiter.version}</version></dependency><!-- JUnit 5 执行引擎(运行测试用例,必须,否则套件里的测试无法执行) --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>${junit.jupiter.version}</version></dependency><!-- JUnit 5 参数化测试(参数解析) --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-params</artifactId><version>${junit.jupiter.version}</version></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-commons</artifactId><version>${junit.platform.version}</version></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-console</artifactId><version>${junit.platform.version}</version></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-engine</artifactId><version>${junit.platform.version}</version></dependency><!--        测试调度中枢(自定义测试执行逻辑)--><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-launcher</artifactId><version>${junit.platform.version}</version></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-reporting</artifactId><version>${junit.platform.version}</version></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-runner</artifactId><version>${junit.platform.version}</version></dependency><!--        套件执行(运行套件)--><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite</artifactId><version>${junit.platform.version}</version></dependency><!-- 套件 API(定义套件) --><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite-api</artifactId><version>${junit.platform.version}</version></dependency><!-- 套件公共资源 --><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite-commons</artifactId><version>${junit.platform.version}</version></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite-engine</artifactId><version>${junit.platform.version}</version></dependency><!-- Hamcrest 核心匹配器 --><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest</artifactId><version>2.2</version></dependency><!--endregion        -->

1. 基本注解

核心注解

import org.junit.jupiter.api.*;class BasicAnnotationsTest {@BeforeAllstatic void setUpClass() {System.out.println("在所有测试方法之前执行一次");}@BeforeEachvoid setUp() {System.out.println("在每个测试方法之前执行");}@Testvoid testMethod() {System.out.println("测试方法");}@AfterEachvoid tearDown() {System.out.println("在每个测试方法之后执行");}@AfterAllstatic void tearDownClass() {System.out.println("在所有测试方法之后执行一次");}
}

测试生命周期注解

import org.junit.jupiter.api.*;@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LifecycleTest {private int counter = 0;@BeforeAllvoid classSetup() {// 由于使用 PER_CLASS,@BeforeAll 可以是实例方法System.out.println("类初始化");}@Testvoid test1() {counter++;System.out.println("Counter: " + counter);}@Testvoid test2() {counter++;System.out.println("Counter: " + counter);}
}

2. 断言

基本断言

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;class AssertionsTest {@Testvoid standardAssertions() {assertEquals(2, 1 + 1);assertEquals(4, 2 * 2, "可选的失败消息");assertTrue('a' < 'b', () -> "断言消息可以延迟计算");}@Testvoid groupedAssertions() {// 所有断言都会执行,所有失败会一起报告assertAll("person",() -> assertEquals("John", "John"),() -> assertEquals("Doe", "Doe"));}@Testvoid exceptionTesting() {Exception exception = assertThrows(ArithmeticException.class, () -> {int result = 1 / 0;});assertEquals("/ by zero", exception.getMessage());}@Testvoid timeoutTest() {assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {// 如果超时会被立即中断Thread.sleep(1000);});}
}

Hamcrest 断言

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;class HamcrestTest {@Testvoid hamcrestAssertions() {assertThat("test", is("test"));assertThat(Arrays.asList(1, 2, 3), hasSize(3));assertThat("hello world", containsString("world"));assertThat(100.0, closeTo(99.0, 2.0));}
}

3. 假设

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;class AssumptionsTest {@Testvoid testOnlyOnCiServer() {Assumptions.assumeTrue("CI".equals(System.getenv("ENV")));// 只有在 CI 环境下才会执行的测试}@Testvoid testOnlyOnDeveloperWorkstation() {Assumptions.assumeTrue("DEV".equals(System.getenv("ENV")),() -> "跳过:不在开发环境");}@Testvoid testInAllEnvironments() {Assumptions.assumingThat("CI".equals(System.getenv("ENV")),() -> {// 只有在 CI 环境下才会执行的断言assertEquals(2, 1 + 1);});// 在所有环境下都会执行的断言assertTrue(true);}
}

4. 显示名称和嵌套测试

显示名称

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;@DisplayName("计算器测试")
class DisplayNameTest {@Test@DisplayName("🔢 加法测试")void testAddition() {assertEquals(2, 1 + 1);}@Test@DisplayName("😱 除法测试 - 除以零")void testDivisionByZero() {assertThrows(ArithmeticException.class, () -> {int result = 1 / 0;});}
}

嵌套测试

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;class StackTest {Stack<Object> stack;@Testvoid isInstantiatedWithNew() {new Stack<>();}@Nestedclass WhenNew {@BeforeEachvoid createNewStack() {stack = new Stack<>();}@Testvoid isEmpty() {assertTrue(stack.isEmpty());}@Nestedclass AfterPushing {String anElement = "an element";@BeforeEachvoid pushAnElement() {stack.push(anElement);}@Testvoid isNotEmpty() {assertFalse(stack.isEmpty());}@Testvoid returnsElementWhenPopped() {assertEquals(anElement, stack.pop());}}}
}

5. 参数化测试

基本参数化测试

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;class ParameterizedTests {@ParameterizedTest@ValueSource(strings = {"racecar", "radar", "able was I ere I saw elba"})void palindromes(String candidate) {assertTrue(StringUtils.isPalindrome(candidate));}@ParameterizedTest@CsvSource({"apple, 1","banana, 2","'lemon, lime', 3"})void testWithCsvSource(String fruit, int rank) {assertNotNull(fruit);assertTrue(rank > 0);}@ParameterizedTest@CsvFileSource(resources = "/test-data.csv")void testWithCsvFileSource(String name, int age) {assertNotNull(name);assertTrue(age >= 0);}@ParameterizedTest@MethodSource("stringProvider")void testWithMethodSource(String argument) {assertNotNull(argument);}static Stream<String> stringProvider() {return Stream.of("apple", "banana");}@ParameterizedTest@ArgumentsSource(MyArgumentsProvider.class)void testWithArgumentsSource(String argument) {assertNotNull(argument);}static class MyArgumentsProvider implements ArgumentsProvider {@Overridepublic Stream<? extends Arguments> provideArguments(ExtensionContext context) {return Stream.of("apple", "banana").map(Arguments::of);}}
}

6. 动态测试

import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;import java.util.stream.Stream;class DynamicTests {@TestFactoryStream<DynamicTest> dynamicTestsFromStream() {return Stream.of("A", "B", "C").map(str -> DynamicTest.dynamicTest("Test " + str, () -> assertTrue(str.length() == 1)));}@TestFactoryStream<DynamicTest> generateRandomNumberOfTests() {// 生成随机数量的测试Iterator<String> inputGenerator = Arrays.asList("A", "B", "C", "D").iterator();return Stream.generate(() -> {if (inputGenerator.hasNext()) {String input = inputGenerator.next();return DynamicTest.dynamicTest("Dynamic Test for " + input,() -> assertNotNull(input));}return null;}).takeWhile(Objects::nonNull);}
}

7. 测试执行顺序

import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTests {@Test@Order(3)void testC() {System.out.println("Test C");}@Test@Order(1)void testA() {System.out.println("Test A");}@Test@Order(2)void testB() {System.out.println("Test B");}
}@TestMethodOrder(MethodOrderer.Random.class)
class RandomOrderTests {// 测试方法会以随机顺序执行
}

8. 扩展模型

自定义扩展

import org.junit.jupiter.api.extension.*;class LoggingExtension implements BeforeEachCallback, AfterEachCallback {@Overridepublic void beforeEach(ExtensionContext context) {System.out.println("开始测试: " + context.getDisplayName());}@Overridepublic void afterEach(ExtensionContext context) {System.out.println("结束测试: " + context.getDisplayName());}
}@ExtendWith(LoggingExtension.class)
class ExtendedTest {@Testvoid testWithExtension() {// 这个测试会自动应用 LoggingExtension}
}

参数解析器

import org.junit.jupiter.api.extension.ParameterResolver;class RandomNumberParameterResolver implements ParameterResolver {@Overridepublic boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {return parameterContext.getParameter().getType() == int.class;}@Overridepublic Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {return new Random().nextInt(100);}
}@ExtendWith(RandomNumberParameterResolver.class)
class ParameterResolverTest {@Testvoid testWithInjectedParameter(@Random int number) {assertTrue(number >= 0 && number < 100);}
}

9. 条件测试

import org.junit.jupiter.api.condition.*;class ConditionalExecutionTest {@Test@EnabledOnOs(OS.WINDOWS)void onlyOnWindows() {// 只在 Windows 上运行}@Test@DisabledOnOs(OS.MAC)void notOnMac() {// 不在 Mac 上运行}@Test@EnabledOnJre(JRE.JAVA_11)void onlyOnJava11() {// 只在 Java 11 上运行}@Test@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")void onlyOn64BitArchitecture() {// 只在 64 位架构上运行}@Test@EnabledIfEnvironmentVariable(named = "ENV", matches = "ci")void onlyOnCiServer() {// 只在 CI 服务器上运行}@Test@EnabledIf("customCondition")void basedOnCustomCondition() {// 基于自定义条件运行}boolean customCondition() {return true;}
}

10. 测试接口和默认方法

interface TestLifecycleLogger {@BeforeAllstatic void beforeAllTests() {System.out.println("Before all tests");}@AfterAllstatic void afterAllTests() {System.out.println("After all tests");}@BeforeEachdefault void beforeEachTest(TestInfo testInfo) {System.out.println("Before test: " + testInfo.getDisplayName());}
}interface TimeExecutionLogger {@Testdefault void testTimeExecution() {long start = System.currentTimeMillis();// 测试逻辑long duration = System.currentTimeMillis() - start;System.out.println("Test executed in: " + duration + "ms");}
}class InterfaceTest implements TestLifecycleLogger, TimeExecutionLogger {// 自动继承接口中的测试方法
}

11. 测试模板

import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;class TestTemplateTest {@TestTemplate@ExtendWith(MyTestTemplateInvocationContextProvider.class)void testTemplate(String parameter) {assertNotNull(parameter);}
}class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {@Overridepublic boolean supportsTestTemplate(ExtensionContext context) {return true;}@Overridepublic Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {return Stream.of("foo", "bar").map(this::invocationContext);}private TestTemplateInvocationContext invocationContext(String parameter) {return new TestTemplateInvocationContext() {@Overridepublic String getDisplayName(int invocationIndex) {return "Parameter: " + parameter;}@Overridepublic List<Extension> getAdditionalExtensions() {return Collections.singletonList(new ParameterResolver() {@Overridepublic boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {return parameterContext.getParameter().getType() == String.class;}@Overridepublic Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {return parameter;}});}};}
}

12. 测试套件

import org.junit.platform.suite.api.*;@Suite
@SelectPackages("com.example.tests")
@IncludeClassNamePatterns(".*Test")
@ExcludeTags("slow")
@IncludeEngines("junit-jupiter")
public class TestSuite {// 运行指定包中的所有测试,排除标记为 slow 的测试
}

这里基本已经涵盖了 JUnit 5 的主要功能。根据具体需求,我们可以选择适合的功能来编写高效、可维护的测试代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/983938.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PBOOTCMS打开网站提示错误信息:执行SQL发生错误!错误: no such table:ay_config的解决方法

在使用 PBOOTCMS 搭建网站时,部分用户可能会遇到“执行 SQL 发生错误!错误:no such table: ay_config”的提示。此问题通常与数据库路径或配置错误有关。本文将详细介绍问题原因及排查解决方法。一、问题描述 当访问…

发现你的专属色彩:一份为你定制的风格指南

你是否曾在衣柜前犹豫不决,不确定什么颜色真正适合自己?或者在购物时,对着五颜六色的衣服感到选择困难?最近我体验了一款个人色彩分析工具,它让我对穿搭有了全新的认识。 这个工具通过AI技术,帮助用户找到最适合…

详细介绍:(ACP广源盛)GSV1175---MIPI/LVDS 输入到 Type-C/DisplayPort 1.2 输出且集成嵌入式 MCU 的信号转换器

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025最新古董回收交易公司权威榜单:古玩家具/瓷器/书画/钱币/银元/铜器/玉器/老物件回收优质品牌排名

推开北京丰宝斋那扇厚重的木门,淡淡的樟木香气混合着旧纸墨的味道扑面而来,一位老师傅正用放大镜仔细端详着一枚清末龙洋的边缘齿纹,阳光透过窗格在他花白的头发上投下光影。 对于藏家而言,为承载历史与情感的老物…

2025年口碑好的气动真空吸盘厂家最新推荐排行榜

2025年口碑好的气动真空吸盘厂家推荐排行榜行业背景与市场趋势随着工业自动化水平的不断提升,气动真空吸盘作为自动化生产线上的关键部件,其市场需求持续增长。2025年,全球气动真空吸盘市场规模预计将达到15亿美元,…

2025年稳压器行业十大标杆品牌

这份榜单不是冰冷的数字游戏,是我们联合《2025 年全自动稳压器行业白皮书》与网易新闻客户端数据,耗时两月打磨的 “工业避坑指南”。排名从三大维度加权计算,拒绝绝对化表述,只聚焦 “如何解决问题”。一、问题:…

2025年质量好的不锈钢/不锈钢拖把池厂家最新TOP实力排行

2025年质量好的不锈钢/不锈钢拖把池厂家TOP实力排行行业背景与市场趋势随着人们生活品质的不断提升和健康环保意识的增强,不锈钢家居产品近年来在厨卫领域获得了快速发展。不锈钢材质以其耐腐蚀、易清洁、环保无毒等特…

2025年优质的食品保鲜制氮机/小型制氮机厂家实力及用户口碑排行榜

2025年优质的食品保鲜制氮机/小型制氮机厂家实力及用户口碑排行榜行业背景与市场趋势随着食品工业的快速发展和消费者对食品保鲜要求的不断提高,制氮机在食品保鲜领域的应用日益广泛。氮气作为一种惰性气体,能有效抑…

2025年聚丙烯酰胺哪家强?五大权威供应商运输与实力全解析

在环保水处理、矿业洗选、造纸印染等领域,聚丙烯酰胺(PAM)作为关键助剂,其品质稳定性、选型精准度与运输效率直接影响企业生产效益。面对市场上鱼龙混杂的供应商,如何挑选权威可靠的合作伙伴?以下结合产品实力、…

博客园博文管理与维护规范(基于随笔/文章/日记+分类/标签/合集功能)

一、核心原则场景匹配优先:根据内容的「公开范围」「用途属性」「阅读场景」选择博文类型,避免功能混用导致管理混乱; 标记体系一致:分类、标签、合集遵循「层级清晰、语义统一、最小必要」原则,降低检索和维护成…

2025年热门的310S耐高温不锈钢焊管厂家最新权威推荐排行榜

2025年热门的310S耐高温不锈钢焊管厂家权威推荐排行榜行业背景与市场趋势随着全球工业化的不断推进和高端制造业的快速发展,310S耐高温不锈钢焊管作为关键工业材料,在石油化工、电力能源、航空航天等领域的应用日益广…

数据的内外符合精度例子

概念比较简单,不再赘述,代码如下:1 clear2 clc3 L=[10.01 10.03 10.02 10.04 10.00];45 sizeL=size(L,2);6 %算数平均值7 lMean=sum(L)/sizeL;89 %观测值改正数observe modify 10 oM=lMean-L; 11 12 %内符合中误差i…

2025 年 12 月液压滑环厂家权威推荐榜:高速/半导体/多通道高压/耐高压/定制液压滑环,尖端密封与长效稳定之选

2025 年 12 月液压滑环厂家权威推荐榜:高速/半导体/多通道高压/耐高压/定制液压滑环,尖端密封与长效稳定之选 在工业自动化、高端制造与精密设备领域,液压滑环作为实现旋转部件与静止部件之间流体介质(如液压油、冷…

PBOOTCMS添加子管理员后,点击清理缓存按钮提示您的账号权限不足,您无法执行该操作!的处理方法

一、问题描述 当您为 PBOOTCMS 添加子管理员后,子管理员尝试点击后台的“清理缓存”按钮时,系统提示“您的账号权限不足,您无法执行该操作!”。此问题通常与管理员权限校验逻辑有关。二、问题原因分析原因分类 具体…

2025年热门的生涯规划教育解决方案/生涯规划设备校园应用优选榜

2025年热门的生涯规划教育解决方案/生涯规划设备校园应用优选榜 开篇:行业背景与市场趋势 随着教育改革的深入推进和就业市场的变化,生涯规划教育已成为各级学校的重要课题。2025年,《普通高中育人方式改革指导意…

2025年知名的铝合金伸缩门厂家推荐及选购参考榜

2025年知名的铝合金伸缩门厂家推荐及选购参考榜行业背景与市场趋势随着城市化进程的加快和智能建筑需求的增长,铝合金伸缩门作为现代建筑出入口管理的重要组成部分,正迎来新一轮发展机遇。2025年,中国铝合金伸缩门市…

2025 年 12 月智能仓储物流公司权威推荐榜:自动化、冷链、医药、电商仓储等全场景物流服务商深度解析与实力甄选

2025 年 12 月智能仓储物流公司权威推荐榜:自动化、冷链、医药、电商仓储等全场景物流服务商深度解析与实力甄选 随着全球供应链数字化转型的加速与国内产业升级的深化,物流行业正经历一场由技术驱动的深刻变革。智能…

2025年知名的110KV断路器/断路器高评价厂家推荐榜

2025年知名的110KV断路器/断路器高评价厂家推荐榜行业背景与市场趋势随着中国电力基础设施建设的持续升级和智能电网的快速发展,110KV断路器作为电力系统中的关键设备,市场需求呈现稳定增长态势。据行业数据显示,20…

2025年热门的翻身护理床厂家最新权威实力榜

2025年热门的翻身护理床厂家权威实力榜行业背景与市场趋势随着全球老龄化进程加速和医疗护理需求增长,翻身护理床市场正迎来前所未有的发展机遇。根据行业数据显示,2024年全球护理床市场规模已达到120亿美元,预计到…

2025年边坡防护网厂家推荐榜单:5 家主动防护网+被动防护网厂家实力盘点

在公路建设、矿山开采、地质灾害治理等诸多工程领域中,边坡安全始终是不可逾越的核心防线。边坡的稳定性直接关系到工程进度、人员安全与周边生态环境,而边坡防护网作为针对性的防护手段,凭借其灵活的适配性与可靠的…