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

API与单元测试自动化

概述

在现代软件开发中,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.jsqmd.com/news/57941/

相关文章:

  • PBOOTCMS打开网站提示错误信息:执行SQL发生错误!错误: no such table:ay_config的解决方法
  • 发现你的专属色彩:一份为你定制的风格指南
  • 详细介绍:(ACP广源盛)GSV1175---MIPI/LVDS 输入到 Type-C/DisplayPort 1.2 输出且集成嵌入式 MCU 的信号转换器
  • 2025最新古董回收交易公司权威榜单:古玩家具/瓷器/书画/钱币/银元/铜器/玉器/老物件回收优质品牌排名
  • 2025年口碑好的气动真空吸盘厂家最新推荐排行榜
  • 2025年稳压器行业十大标杆品牌
  • 2025年质量好的不锈钢/不锈钢拖把池厂家最新TOP实力排行
  • 2025年优质的食品保鲜制氮机/小型制氮机厂家实力及用户口碑排行榜
  • 2025年聚丙烯酰胺哪家强?五大权威供应商运输与实力全解析
  • 博客园博文管理与维护规范(基于随笔/文章/日记+分类/标签/合集功能)
  • 2025年热门的310S耐高温不锈钢焊管厂家最新权威推荐排行榜
  • 数据的内外符合精度例子
  • 2025 年 12 月液压滑环厂家权威推荐榜:高速/半导体/多通道高压/耐高压/定制液压滑环,尖端密封与长效稳定之选
  • PBOOTCMS添加子管理员后,点击清理缓存按钮提示您的账号权限不足,您无法执行该操作!的处理方法
  • 2025年热门的生涯规划教育解决方案/生涯规划设备校园应用优选榜
  • 2025年知名的铝合金伸缩门厂家推荐及选购参考榜
  • 2025 年 12 月智能仓储物流公司权威推荐榜:自动化、冷链、医药、电商仓储等全场景物流服务商深度解析与实力甄选
  • 2025年知名的110KV断路器/断路器高评价厂家推荐榜
  • 2025 年 12 月应急救援背囊厂家权威推荐榜:专业级背囊/急救背囊/复苏背囊/清创手术背囊,守护生命安全的硬核装备之选
  • 2025年重庆水泵电机维修公司权威推荐榜单:电机马达维修‌/三菱电机维修‌/直流电机维修‌源头公司精选
  • 2025 年 12 月急救箱厂家权威推荐榜:院前急救箱、拉杆急救箱、综合急救箱等专业应急装备实力解析
  • AI元人文构想:停止追问,开始构建
  • 2025年知名的化工螺杆真空泵厂家推荐及采购指南
  • 2025年12月宣传片拍摄制作公司最新推荐:专业与创意并重的视觉服务伙伴
  • P2097 资料分发
  • 深入解析:深入解析 Spring Boot 自动配置:原理、实践与进阶​
  • PbootCMS模板安装后首页样式错乱问题的解决指南
  • Win11+Zorin OS双系统安装记录
  • 2025控油洗发水怎么选?6款亲测专治油扁塌还强韧蓬松
  • pycharm底部一直显示正在编制索引