单元测试是软件开发过程中的一种验证手段,它针对最小的可测试部分(通常是函数或方法)进行检查和验证。其实单元测试还是挺重要的,不过国内很多公司的项目其实并没有做好单元测试,或者根本就没做单元测试,原因可能是项目周期比较紧张,开发时间不充足,所以就省略了单元测试,也有可能是领导不重视单元测试。之前工作中做单元测试主要用到JUnit和TestNG,做覆盖统计主要用的JaCoCo。不过本篇主要总结JUnit5的知识点及用法。JUnit5官网:JUnit 5
目录
[2.JUnit 5 的主要注解](#2.JUnit 5 的主要注解)
[3.JUnit 5 断言(Assertions)](#3.JUnit 5 断言(Assertions))
[4.JUnit 5 测试方法](#4.JUnit 5 测试方法)
[5. JUnit 5 测试执行控制](#5. JUnit 5 测试执行控制)
[6.JUnit 5 测试输出](#6.JUnit 5 测试输出)
[7.JUnit 5 测试辅助功能](#7.JUnit 5 测试辅助功能)
[8.JUnit 5 错误处理和异常测试](#8.JUnit 5 错误处理和异常测试)
[9.JUnit 5 测试依赖注入](#9.JUnit 5 测试依赖注入)
[10.JUnit 5 测试监听器](#10.JUnit 5 测试监听器)
[11.JUnit 5 测试配置](#11.JUnit 5 测试配置)
[12. JUnit 5 测试动态生成](#12. JUnit 5 测试动态生成)
[13.JUnit 5 测试参数化](#13.JUnit 5 测试参数化)
[14.JUnit 5 测试并行执行](#14.JUnit 5 测试并行执行)
[15.JUnit 5 测试可读性](#15.JUnit 5 测试可读性)
[16.JUnit 5 测试条件](#16.JUnit 5 测试条件)
[17.Spring Boot项目集成Junit5](#17.Spring Boot项目集成Junit5)
1.Junit5简介和环境搭建
特性 | 描述 |
---|---|
JUnit 5 架构 | JUnit 5 由三个主要模块组成:JUnit Platform、JUnit Jupiter 和 JUnit Vintage。 |
JUnit Platform | 提供了一个测试框架的运行时平台,允许IDE和构建工具在JVM上启动和请求测试。 |
JUnit Jupiter | 提供了新的编程模型和扩展模型,用于编写测试。 |
JUnit Vintage | 允许JUnit 5运行JUnit 3和JUnit 4的测试。 |
环境搭建 | 使用Maven或Gradle将JUnit 5添加到项目中。 |
环境搭建:
使用Maven或Gradle将JUnit 5添加到项目中。
XML
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
2.JUnit 5 的主要注解
注解 | 描述 | 代码示例 |
---|---|---|
@BeforeEach |
在每个测试方法执行之前运行的方法。 | java @BeforeEach public void init() { ... } |
@AfterEach |
在每个测试方法执行之后运行的方法。 | @AfterEach public void tearDown() { ... } |
@BeforeAll |
在所有测试方法执行之前,整个类中只运行一次。 | @BeforeAll public static void setUpBeforeClass() { ... } |
@AfterAll |
在所有测试方法执行之后,整个类中只运行一次。 | @AfterAll public static void tearDownAfterClass() { ... } |
@Test |
标记一个方法为测试方法。 | @Test public void myTestMethod() { ... } |
@RepeatedTest |
允许测试方法重复执行指定次数。 | @RepeatedTest(10) public void repeatedTestMethod() { ... } |
@ParameterizedTest |
用于参数化测试。 | @ParameterizedTest public void parameterizedTestMethod(int param) { ... } |
@MethodSource |
与@ParameterizedTest 一起使用,提供测试参数。 |
java @MethodSource("parameters") public void parameterizedTestMethod(int param) { ... } static Stream<Object[]> parameters() { return Stream.of(new Object[]{1}); } |
3.JUnit 5 断言(Assertions)
断言方法 | 描述 | 代码示例 |
---|---|---|
assertAll() |
允许组合多个断言,如果任何一个断言失败,测试会立即失败。 | assertAll("Test Group", () -> assertEquals(2, 1 + 1), () -> assertEquals("foo", "bar")); |
assertNotNull() |
验证对象不是null 。 |
assertNotNull("Object should not be null", myObject); |
assertNull() |
验证对象是null 。 |
assertNull("Object should be null", myObject); |
assertTrue() |
验证条件为true 。 |
assertTrue("Should be true", condition); |
assertFalse() |
验证条件为false 。 |
assertFalse("Should be false", condition); |
assertEquals() |
验证两个值是否相等。 | assertEquals(2, 1 + 1); |
assertNotEquals() |
验证两个值是否不相等。 | assertNotEquals("Should not be equal", 2, 3); |
assertSame() |
验证两个引用是否指向同一个对象。 | Object obj1 = new Object(); Object obj2 = obj1; assertSame(obj1, obj2); |
assertNotSame() |
验证两个引用是否指向不同的对象。 | assertNotSame("Should not be same", obj1, obj2); |
以上是JUnit 5中常用的断言方法,它们帮助开发者验证测试用例中的预期结果是否符合实际结果。
4.JUnit 5 测试方法
特性 | 描述 | 代码示例 |
---|---|---|
测试方法 | 使用@Test 注解标记的方法,JUnit 5将自动运行这些方法作为测试。 |
@Test public void testMethod() { ... } |
测试方法参数 | 测试方法可以接收参数,如测试数据。 | @ParameterizedTest public void testMethod(String data) { ... } |
超时测试 | 使用@Timeout 注解设置测试方法的最大执行时间。 |
@Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) public void testMethod() { ... } |
动态测试 | 使用@DynamicTest 注解创建动态生成的测试。 |
java @TestFactory Stream<DynamicTest> dynamicTestsStream() { return Stream.of("Test1", "Test2") .map(data -> DynamicTest.dynamicTest(data, () -> { ... } )); } |
条件测试 | 使用@EnabledIf 或@DisabledIf 注解根据条件启用或禁用测试。 |
@EnabledIf("expression") @Test public void testMethod() { ... } |
5. JUnit 5 测试执行控制
控制方式 | 描述 | 代码示例 |
---|---|---|
标签(Tags) | 使用@Tag 注解给测试分类,可以通过标签过滤运行特定测试。 |
@Tag("fast") @Test public void fastTestMethod() { ... } |
测试配置(Test Configuration) | 使用@TestInstance 注解控制测试方法的生命周期。 |
@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
测试筛选器(Test Filters) | 使用JUnit 5的内置筛选器来选择要运行的测试。 | 在命令行中使用 -@include 或 --exclude 选项。 |
测试依赖(Test Dependencies) | 使用@ExtendWith 注解定义测试类或方法的依赖关系。 |
@ExtendWith(CustomExtension.class) public class MyTestClass { ... } |
重复测试(Repeated Tests) | 使用@RepeatedTest 注解让测试方法重复执行。 |
@RepeatedTest(5) public void repeatedTestMethod() { ... } |
参数化测试(Parameterized Tests) | 使用@ParameterizedTest 和@MethodSource 注解执行参数化测试。 |
@ParameterizedTest @MethodSource("dataProvider") public void parameterizedTest(int param) { ... } static Stream<?> dataProvider() { return Stream.of(1, 2, 3); } |
临时文件夹(Temporary Folders) | 使用@TempDir 注解为测试方法提供临时文件夹路径。 |
@Test public void testWithTempFolder(@TempDir Path tempDir) { ... } |
以上是JUnit 5中测试执行控制的一些关键特性,它们允许开发者更灵活地控制测试的执行流程和条件。
6.JUnit 5 测试输出
特性 | 描述 | 代码示例 |
---|---|---|
断言消息 | 使用断言方法的重载版本,提供自定义的失败消息。 | assertEquals("List should contain 'B'", "A", list.get(1)); |
日志记录 | 使用@LogMessageRule 注解捕获日志消息。 |
@Rule public LogMessageRule rule = new LogMessageRule(); @Test public void testLogCapture() { rule.expect(WARNING); logger.warn("This is a warning message"); } |
测试输出(Test Output) | 使用@TestInfo 获取测试信息,如测试方法名称、显示名称等。 |
@Test public void testWithTestInfo(TestInfo testInfo) { System.out.println("Running test: " + testInfo.getDisplayName()); } |
测试模板方法(Test Template Methods) | 使用@TestTemplate 注解定义模板方法,结合@ExtendWith 注解使用。 |
@TestTemplate public void testTemplateMethod(MyCustomExtension ext) { ... } @ExtendWith(MyCustomExtension.class) public void extendWithMethod() { ... } |
动态测试(Dynamic Tests) | 生成动态测试用例,返回Stream 的DynamicTest 。 |
@TestFactory Stream<DynamicTest> dynamicTests() { return Stream.of("A", "B", "C") .map(input -> DynamicTest.dynamicTest(input, () -> assertEquals(1, input.length()))); } |
7.JUnit 5 测试辅助功能
功能 | 描述 | 代码示例 |
---|---|---|
假设(Assumptions) | 使用假设来避免在不满足特定条件时执行测试。 | assumeTrue("This test assumes JDK 11 or higher", javaVersion >= 11); |
测试时钟(Test Clock) | 使用@MockClock 注解模拟时间,用于时间相关的测试。 |
@Test @MockClock("12:00:00") public void testWithMockClock() { ... } |
测试资源(Test Resources) | 使用@RegisterExtension 注解注册测试资源,如临时文件、数据库连接等。 |
@RegisterExtension public TemporaryFolder folder = new TemporaryFolder(); @Test public void testWithTemporaryFolder() { Path path = folder.getRoot().toPath(); ... } |
测试规则(Test Rules) | 使用测试规则来为测试方法提供额外的行为,如日志捕获、重复测试等。 | @Rule public TestRule logWatcher = new LogWatcher(); @Test public void testWithLogWatcher() { ... } |
条件测试(Conditional Tests) | 根据系统属性或环境变量的条件执行测试。 | @EnabledIfEnvironment("os.name == 'Windows 10'") @Test public void windows10OnlyTest() { ... } |
测试配置参数(Test Configuration Parameters) | 从命令行或配置文件中读取参数,并在测试中使用。 | @Test public void testWithConfigurationParameter(@ConfiguredParameter("timeout") int timeout) { ... } |
测试模板方法(Test Template Methods) | 允许为测试提供自定义的执行逻辑。 | @TestTemplate public void testTemplateMethod(MyCustomExtension ext) { ... } @ExtendWith(MyCustomExtension.class) public void extendWithMethod() { ... } |
这些辅助功能增强了JUnit 5的测试能力,使得测试更加灵活和强大
8.JUnit 5 错误处理和异常测试
特性 | 描述 | 代码示例 |
---|---|---|
期望异常(Expected Exceptions) | 使用assertThrows 来验证方法是否抛出了特定的异常。 |
Assertions.assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("bad argument"); }); |
异常测试(Exception Testing) | 使用@Test 注解的expectedExceptions 属性来测试预期的异常。 |
@Test(expectedExceptions = ArithmeticException.class) public void testDivideByZero() { int i = 1 / 0; } |
断言异常内容(Asserting Exception Content) | 捕获异常并验证其内容,如消息或原因。 | Exception exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("error"); }); assertEquals("error", exception.getMessage()); |
错误收集(Error Collecting) | 使用assertAll 来执行多个断言,即使其中一个失败,其他断言也会继续执行。 |
assertAll("test", () -> assertEquals(2, 1 + 1), () -> assertThrows(RuntimeException.class, () -> { throw new RuntimeException(); })); |
软断言(Soft Assertions) | 使用软断言来收集多个失败的断言,而不是在第一个失败时立即停止测试。 | SoftAssertions softly = new SoftAssertions(); softly.assertThat(codePointBefore('a')).isEqualTo(-1); softly.assertThat(codePointBefore('A')).isEqualTo(-1); softly.assertAll(); |
这些特性帮助开发者更好地处理测试中的异常情况,确保测试的准确性和健壮性。
9.JUnit 5 测试依赖注入
特性 | 描述 | 代码示例 |
---|---|---|
构造器注入 | 使用@Autowired 注解在测试类构造器中注入依赖。 |
@SpringBootTest public class MySpringBootTest { @Autowired private MyService service; } |
字段注入 | 使用@Inject 注解在字段上注入依赖。 |
public class MyTestClass { @Inject private MyService service; } |
方法参数注入 | 使用@InjectMocks 注解在测试方法的参数上注入依赖。 |
@Test public void testMethod(@Mocked MyDependency dependency) { ... } |
模块化测试 | 使用@ExtendWith 注解和自定义扩展来模块化测试逻辑。 |
@ExtendWith(MyExtension.class) public class MyTestClass { ... } |
测试实例化 | 使用@TestInstance 注解控制测试类的实例化方式。 |
@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
测试上下文管理 | 使用@TestInfo 获取测试上下文信息,如测试方法名称、测试类等。 |
public class MyTestClass { @Test public void testMethod(TestInfo testInfo) { System.out.println(testInfo.getDisplayName()); } } |
依赖注入是现代测试框架中的重要特性,它允许测试代码更加模块化和可重用。JUnit 5通过集成Spring等框架,提供了强大的依赖注入支持。
10.JUnit 5 测试监听器
特性 | 描述 | 代码示例 |
---|---|---|
监听器(Listeners) | 使用@ExtendWith 注解添加监听器,监听测试的生命周期事件。 |
@ExtendWith(MyTestWatcher.class) public class MyTestClass { ... } |
测试执行监听器(Test Execution Listeners) | 实现TestExecutionListener 接口,监听测试的执行过程。 |
public class MyTestExecutionListener implements TestExecutionListener { ... } |
测试实例监听器(Test Instance Listeners) | 实现TestInstanceListener 接口,监听测试实例的创建和生命周期。 |
public class MyTestInstanceListener implements TestInstanceListener { ... } |
测试生命周期监听器(Test Lifecycle Listeners) | 实现TestLifecycleListener 接口,监听测试的整个生命周期。 |
public class MyTestLifecycleListener implements TestLifecycleListener { ... } |
测试失败监听器(Test Failure Listeners) | 实现TestFailureListener 接口,监听测试失败事件。 |
public class MyTestFailureListener implements TestFailureListener { ... } |
测试告警监听器(Test Alerting Listeners) | 实现TestAlerting 接口,对测试失败进行告警。 |
public class MyTestAlerting implements TestAlerting { ... } |
测试监听器是JUnit 5中用于监听和响应测试事件的强大机制,它们可以用来扩展JUnit 5的功能,如测试报告生成、性能监控等。
11.JUnit 5 测试配置
配置项 | 描述 | 使用示例 |
---|---|---|
全局配置 | 通过junit-platform.properties 文件进行全局配置。 |
junit.jupiter.conditions.include-classes-with-at-least-one-method = true |
测试方法配置 | 使用注解在测试方法上指定配置。 | @Test @DisplayName("A test with custom display name") void testMethod() { ... } |
测试类配置 | 使用注解在测试类上指定配置。 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
测试模板配置 | 使用注解在测试模板方法上指定配置。 | @TestTemplate public void testTemplate() { ... } |
参数化测试配置 | 使用注解在参数化测试方法上指定配置。 | @ParameterizedTest(name = "{index} => {0} + {1} = {2}") @MethodSource("dataProvider") void parameterizedTest(int a, int b, int expected) { ... } |
测试过滤器配置 | 使用注解或命令行参数来过滤测试。 | 在命令行中使用 --filter 选项。 |
测试重复配置 | 使用注解在测试方法上指定重复次数。 | @RepeatedTest(3) void repeatedTestMethod() { ... } |
测试超时配置 | 使用注解在测试方法上指定执行超时时间。 | @Test @Timeout(duration = 500, unit = TimeUnit.MILLISECONDS) void testMethod() { ... } |
测试配置是JUnit 5中用于定制测试行为的重要特性,它允许开发者根据需要调整测试的执行方式。
12. JUnit 5 测试动态生成
特性 | 描述 | 代码示例 |
---|---|---|
动态测试(Dynamic Tests) | 允许在测试执行期间动态生成测试用例。 | @TestFactory Stream<DynamicTest> dynamicTests() { return Stream.of("foo", "bar") .map(s -> DynamicTest.dynamicTest(s, () -> assertNotEquals(0, s.length()))); } |
测试工厂(Test Factories) | 创建动态测试的方法,可以返回Stream 或Iterable 的DynamicTest 。 |
@TestFactory Stream<DynamicTest> dynamicTestsWithStream() { return Stream.of(1, 2, 3) .map(i -> DynamicTest.dynamicTest("Test with " + i, () -> {})); } |
测试模板方法(Test Template Methods) | 使用模板方法来定义测试逻辑,并通过扩展执行不同的测试用例。 | @TestTemplate void testWithCustomProvider(Object o) { ... } @ExtendWith(CustomProviderExtension.class) void extendWithCustomProvider() { ... } |
测试扩展(Test Extensions) | 自定义扩展可以介入测试执行的各个阶段,实现自定义逻辑。 | public class CustomExtension implements TestExecutionListener { ... } |
动态生成测试用例是JUnit 5中一个强大的特性,它允许开发者根据需要灵活地生成测试用例,从而提高测试的复用性和灵活性。
13.JUnit 5 测试参数化
特性 | 描述 | 代码示例 |
---|---|---|
参数化测试(Parameterized Tests) | 允许为单个测试方法提供多个输入参数。 | @ParameterizedTest @MethodSource("numbersProvider") void parameterizedTest(int number) { ... } static Stream<Integer> numbersProvider() { return Stream.of(1, 2, 3); } |
方法源(Method Source) | 提供测试参数的来源,可以是静态方法或字段。 | static Stream<Arguments> numbersProvider() { return Stream.of(Arguments.of(1), Arguments.of(2), Arguments.of(3)); } |
CSV源(CSV Source) | 使用CSV格式的字符串直接提供测试参数。 | @ParameterizedTest @CsvSource({ "1, 2, 3", "4, 5, 6" }) void parameterizedTest(int a, int b, int c) { ... } |
对象数组源(Object Array Source) | 使用对象数组直接提供测试参数。 | @ParameterizedTest @ArgumentsSource(ObjectArraySource.class) void parameterizedTest(String data) { ... } |
自定义提供器(Custom Providers) | 创建自定义的参数提供器来生成测试参数。 | public class CustomProvider implements ArgumentsProvider { public Stream<? extends Arguments> provideArguments(ExtensionContext context) { return Stream.of(Arguments.of("A", "B")); } } |
参数化测试是JUnit 5中用于测试多种输入组合的强大特性,它允许开发者编写更简洁、更高效的测试代码。
14.JUnit 5 测试并行执行
特性 | 描述 | 代码示例 |
---|---|---|
并行执行(Parallel Execution) | 允许同时运行多个测试,以加快测试套件的执行速度。 | 使用JUnit 5的junit.jupiter.execution.parallel.enabled 配置属性来启用并行执行。 |
测试类级别的并行 | 对整个测试类中的测试方法进行并行执行。 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MyTestClass { ... } |
方法级别的并行 | 对单个测试方法进行并行执行。 | 使用JUnit 5的junit.jupiter.execution.parallel.mode.default 配置属性来设置默认的并行模式。 |
自定义并行策略 | 通过实现TestExecutionListener 接口来自定义测试的并行执行策略。 |
public class CustomParallelismListener implements TestExecutionListener { ... } |
资源隔离 | 确保在并行执行过程中,测试之间的资源是隔离的,避免相互干扰。 | 使用@RegisterExtension 注解的ResourceLock 规则来锁定特定资源。 |
动态测试并行 | 对动态生成的测试用例进行并行执行。 | 在@TestFactory 方法中返回的DynamicTest 流可以被并行执行。 |
测试并行执行是提高测试效率的重要特性,它可以有效减少持续集成(CI)环境中的测试等待时间。
15.JUnit 5 测试可读性
特性 | 描述 | 代码示例 |
---|---|---|
显示名称(Display Name) | 为测试方法提供可读性强的显示名称。 | @DisplayName("Test with custom display name") @Test public void testMethod() { ... } |
嵌套测试(Nested Tests) | 使用嵌套的测试方法来组织测试逻辑,提高测试的可读性。 | @Test public void outerTest() { @Test public void innerTest() { ... } } |
测试描述(Test Description) | 提供测试的描述信息,增强测试的可读性。 | 使用TestInfo 获取测试的描述信息,并在测试日志中展示。 |
断言消息(Assertion Messages) | 在断言失败时提供自定义的消息,帮助理解失败的原因。 | assertEquals("Expected reference equality", obj1, obj2); |
测试模板(Test Templates) | 使用测试模板方法来提供可读性强的测试逻辑。 | @TestTemplate void testWithCustomProvider(Object o) { ... } |
测试可读性是JUnit 5中用于提高测试代码和测试报告可读性的重要特性,它有助于开发者更好地理解和维护测试代码。
16.JUnit 5 测试条件
特性 | 描述 | 代码示例 |
---|---|---|
条件注解(Conditional Annotations) | 根据条件启用或禁用测试,如系统属性、环境变量等。 | @EnabledIf("javaVersion > 11") @Test public void testMethod() { ... } |
系统属性条件(System Property Condition) | 根据系统属性的值来启用或禁用测试。 | @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") @Test public void testMethod() { ... } |
环境变量条件(Environment Variable Condition) | 根据环境变量的值来启用或禁用测试。 | @EnabledIfEnvironmentVariable(named = "CI", matches = "true") @Test public void testMethod() { ... } |
自定义条件(Custom Conditions) | 实现Condition 接口来提供自定义的条件逻辑。 |
public class CustomCondition implements Condition { ... } |
测试配置参数(Test Configuration Parameters) | 从命令行或配置文件中读取参数,并在测试中使用。 | @Test public void testWithConfigurationParameter(@ConfiguredParameter("timeout") int timeout) { ... } |
测试条件是JUnit 5中用于根据环境或配置来控制测试执行的重要特性,它允许开发者灵活地决定哪些测试应该运行。
17.Spring Boot项目集成Junit5
17.1首先,确保你的pom.xml
(Maven)或build.gradle
(Gradle)文件中包含了JUnit 5和Spring Boot Test的依赖。
对于Maven,pom.xml
可能包含以下依赖:
XML
<dependencies>
<!-- Spring Boot Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.5.0</version> <!-- 使用你项目匹配的版本 -->
<scope>test</scope>
</dependency>
</dependencies>
对于Gradle,build.gradle
可能包含以下依赖:
bash
dependencies {
// Spring Boot Test Starter
testImplementation('org.springframework.boot:spring-boot-starter-test:2.5.0') // 使用你项目匹配的版本
}
17.2 创建测试类
创建一个测试类,使用@SpringBootTest
注解来指示Spring Boot为测试提供支持。使用@Test
注解标记测试方法。
这里是一个简单的服务层测试示例:
java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class SomeServiceTest {
@Autowired
private SomeService someService;
@MockBean
private SomeDependency someDependency;
@Test
public void testSomeMethod() {
// 设置mock行为
when(someDependency.someMethod()).thenReturn("expected value");
// 调用待测试的方法
String result = someService.someMethod();
// 验证结果
assertEquals("expected value", result);
// 验证依赖是否被调用
verify(someDependency).someMethod();
}
}
这个例子中,SomeService
是我们想要测试的服务层组件,而SomeDependency
是它的一个依赖项,我们使用@MockBean
注解来创建一个模拟对象。
17.3 运行测试
你可以在IDE中运行测试,或者使用Maven或Gradle的命令行工具来执行测试。
对于Maven,使用以下命令:
mvn test
对于Gradle,使用以下命令:
./gradlew test
这只是一个极简单的示例,在项目的具体需求下,测试会更复杂,包括更多的模拟对象,服务和测试用例等。