# 3.4注解

## 3.4 注解

本节介绍了在测试Spring应用程序时可以使用的注解。 它包括以下主题：

* Spring测试注解
* 标准注解支持
* Spring JUnit 4测试注解
* Spring JUnit Jupiter测试注解
* 测试的元注解支持

### 3.4.1 Spring测试注解

Spring Framework提供了以下一组特定于Spring的注解，你可以在单元和集成测试中结合TestContext框架使用它们。 有关详细信息，请参阅相应的javadoc，包括默认属性值，属性别名和其他详细信息。

Spring的测试注解包括以下内容：

* @BootstrapWith
* @ContextConfiguration
* @WebAppConfiguration
* @ContextHierarchy
* @ActiveProfiles
* @TestPropertySource
* @DirtiesContext
* @TestExecutionListeners
* @Commit
* @Rollback
* @BeforeTransaction
* @AfterTransaction
* @Sql
* @SqlConfig
* @SqlGroup

**@BootstrapWith**

@BootstrapWith是一个类级注解，可用于配置Spring TestContext Framework的引导方式。 具体来说，你可以使用@BootstrapWith指定自定义TestContextBootstrapper。 有关更多详细信息，请参阅有关引导TestContext框架的部分。

**@ContextConfiguration**

@ContextConfiguration定义类级元数据，用于确定如何为集成测试加载和配置ApplicationContext。 具体来说，@ContextConfiguration声明应用程序上下文资源位置或用于加载上下文的带注解的类。

资源位置通常是位于类路径中的XML配置文件或Groovy脚本，而带注解的类通常是@Configuration类。 但是，资源位置也可以引用文件系统中的文件和脚本，带注解的类可以是组件类，依此类推。

以下示例显示了引用XML文件的@ContextConfiguration批注：

```java
@ContextConfiguration("/test-config.xml") 
public class XmlApplicationContextTests {
    // class body...
}
```

以下示例显示了引用类的@ContextConfiguration批注：

```java
@ContextConfiguration(classes = TestConfig.class) 
public class ConfigClassApplicationContextTests {
    // class body...
}
```

作为声明资源位置或带注解的类的替代或补充，你可以使用@ContextConfiguration来声明ApplicationContextInitializer类。 以下示例显示了这种情况：

```java
@ContextConfiguration(initializers = CustomContextIntializer.class) 
public class ContextInitializerTests {
    // class body...
}
```

你也可以选择使用@ContextConfiguration来声明ContextLoader策略。 但请注意，你通常不需要显式配置加载程序，因为默认加载程序支持初始化程序以及资源位置或带注解的类。

以下示例使用位置和加载器：

```java
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) 
public class CustomLoaderXmlApplicationContextTests {
    // class body...
}
```

> @ContextConfiguration支持继承资源位置或配置类以及超类声明的上下文初始化程序。

**@WebAppConfiguration**

@WebAppConfiguration是一个类级别的注解，可用于声明为集成测试加载的ApplicationContext应该是WebApplicationContext。 仅在测试类上存在@WebAppConfiguration可确保为测试加载WebApplicationContext，使用默认值“file:src/main/webapp”作为Web应用程序根目录的路径（即资源） 基本路径）。 在后台使用资源基本路径来创建MockServletContext，它充当测试的WebApplicationContext的ServletContext。

以下示例显示如何使用@WebAppConfiguration批注：

```java
@ContextConfiguration
@WebAppConfiguration 
public class WebAppTests {
    // class body...
}
```

要覆盖默认值，可以使用隐式值属性指定不同的基本资源路径。 支持classpath：和file：资源前缀。 如果未提供资源前缀，则假定该路径是文件系统资源。 以下示例显示如何指定类路径资源：

```java
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") 
public class WebAppTests {
    // class body...
}
```

请注意，@WebAppConfiguration必须与@ContextConfiguration结合使用，可以在单个测试类中，也可以在测试类层次结构中使用。 有关更多详细信息，请参阅@WebAppConfiguration javadoc。

**@ContextHierarchy**

@ContextHierarchy是一个类级别注解，用于为集成测试定义ApplicationContext实例的层次结构。 应使用一个或多个@ContextConfiguration实例的列表声明@ContextHierarchy，每个实例定义上下文层次结构中的级别。 以下示例演示了在单个测试类中使用@ContextHierarchy（@ContextHierarchy也可以在测试类层次结构中使用）：

```java
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
public class ContextHierarchyTests {
    // class body...
}
```

```java
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
public class WebIntegrationTests {
    // class body...
}
```

如果需要合并或覆盖测试类层次结构中上下文层次结构的给定级别的配置，则必须通过在类层次结构中的每个相应级别为@ContextConfiguration中的name属性提供相同的值来显式命名该级别。 有关更多示例，请参阅上下文层次结构和@ContextHierarchy javadoc。

**@ActiveProfiles**

@ActiveProfiles是一个类级注解，用于在为集成测试加载ApplicationContext时声明哪些bean定义配置文件应该处于活动状态。

以下示例表明dev配置文件应处于活动状态：

```java
@ContextConfiguration
@ActiveProfiles("dev") 
public class DeveloperTests {
    // class body...
}
```

以下示例表明dev和集成配置文件都应处于活动状态：

```java
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) 
public class DeveloperIntegrationTests {
    // class body...
}
```

@ActiveProfiles默认支持继承超类声明的活动bean定义配置文件。 你还可以通过实现自定义ActiveProfilesResolver并使用@ActiveProfiles的解析程序属性对其进行注册来以编程方式解析活动Bean定义概要文件。

**@TestPropertySource**

@TestPropertySource是一个类级别注解，可用于配置属性文件的位置和内联属性，以便为集成测试加载的ApplicationContext添加到环境中的PropertySource集合中。

测试属性源的优先级高于从操作系统环境或Java系统属性加载的属性源，以及应用程序通过@PropertySource或以编程方式声明性地添加的属性源。 因此，测试属性源可用于有选择地覆盖系统和应用程序属性源中定义的属性。 此外，内联属性的优先级高于从资源位置加载的属性。

以下示例演示如何从类路径声明属性文件：

```java
@ContextConfiguration
@TestPropertySource("/test.properties") 
public class MyIntegrationTests {
    // class body...
}
```

以下示例演示如何声明内联属性：

```java
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) 
public class MyIntegrationTests {
    // class body...
}
```

**@DirtiesContext**

@DirtiesContext指示在执行测试期间底层Spring ApplicationContext已被弄脏（即，测试以某种方式修改或损坏它 - 例如，通过更改单例bean的状态）并且应该关闭。当应用程序上下文被标记为脏时，它将从测试框架的缓存中删除并关闭。因此，对于需要具有相同配置元数据的上下文的任何后续测试，都会重建基础Spring容器。

你可以将@DirtiesContext用作同一类或类层次结构中的类级别和方法级别注解。在这种情况下，ApplicationContext在任何此类带注解的方法之前或之后以及当前测试类之前或之后被标记为脏，具体取决于配置的methodMode和classMode。

以下示例说明了各种配置方案的上下文何时会变脏：

* 在当前测试类之前，在类模式设置为BEFORE\_CLASS的类上声明。

```java
@DirtiesContext(classMode = BEFORE_CLASS) 
public class FreshContextTests {
    // some tests that require a new Spring container
}
```

* 在当前测试类之后，在类模式设置为AFTER\_CLASS（即默认类模式）的类上声明。

```java
@DirtiesContext 
public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
```

* 在当前测试类中的每个测试方法之前，在类模式设置为BEFORE\_EACH\_TEST\_METHOD的类上声明时。

```java
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) 
public class FreshContextTests {
    // some tests that require a new Spring container
}
```

* 在当前测试类中的每个测试方法之后，在类模式设置为AFTER\_EACH\_TEST\_METHOD的类上声明。

```java
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) 
public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
```

* 在当前测试之前，在方法模式设置为BEFORE\_METHOD的方法上声明。

```java
@DirtiesContext(methodMode = BEFORE_METHOD) 
@Test
public void testProcessWhichRequiresFreshAppCtx() {
    // some logic that requires a new Spring container
}
```

* 在当前测试之后，在方法模式设置为AFTER\_METHOD（即默认方法模式）的方法上声明。

```java
@DirtiesContext 
@Test
public void testProcessWhichDirtiesAppCtx() {
    // some logic that results in the Spring container being dirtied
}
```

如果在测试中使用@DirtiesContext，其上下文配置为具有@ContextHierarchy的上下文层次结构的一部分，则可以使用hierarchyMode标志来控制如何清除上下文高速缓存。 默认情况下，使用详尽的算法来清除上下文缓存，不仅包括当前级别，还包括共享当前测试常见的祖先上下文的所有其他上下文层次结构。 驻留在公共祖先上下文的子层次结构中的所有ApplicationContext实例将从上下文缓存中删除并关闭。 如果穷举算法对于特定用例而言过度，则可以指定更简单的当前级别算法，如以下示例所示。

```java
@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
public class BaseTests {
    // class body...
}

public class ExtendedTests extends BaseTests {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) 
    public void test() {
        // some logic that results in the child context being dirtied
    }
}
```

有关EXHAUSTIVE和CURRENT\_LEVEL算法的更多详细信息，请参阅DirtiesContext.HierarchyMode javadoc。

**@TestExecutionListeners**

@TestExecutionListeners定义了类级元数据，用于配置应该使用TestContextManager注册的TestExecutionListener实现。 通常，@TestExecutionListeners与@ContextConfiguration一起使用。

以下示例显示如何注册两个TestExecutionListener实现：

```java
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) 
public class CustomTestExecutionListenerTests {
    // class body...
}
```

默认情况下，@TestExecutionListeners支持继承的侦听器。 有关示例和更多详细信息，请参阅javadoc。

**@Commit**

@Commit表示应在测试方法完成后提交事务测试方法的事务。 你可以使用@Commit作为@Rollback（false）的直接替换，以更明确地传达代码的意图。 类似于@Rollback，@Commit也可以声明为类级别或方法级别的注解。

以下示例显示了如何使用@Commit注解：

```java
@Commit 
@Test
public void testProcessWithoutRollback() {
    // ...
}
```

**@Rollback**

@Rollback指示在测试方法完成后是否应回滚事务测试方法的事务。 如果为true，则回滚事务。 否则，提交事务（另请参阅@Commit）。 即使没有显式声明@Rollback，Spring TestContext Framework中的集成测试回滚也默认为true。

当声明为类级别注解时，@Rollback定义测试类层次结构中所有测试方法的默认回滚语义。 当声明为方法级注解时，@Rollback定义特定测试方法的回滚语义，可能会覆盖类级别的@Rollback或@Commit语义。

以下示例导致不回滚测试方法的结果（即，结果提交到数据库）：

```java
@Rollback(false) 
@Test
public void testProcessWithoutRollback() {
    // ...
}
```

**@BeforeTransaction**

@BeforeTransaction指示在启动事务之前应该运行带注解的void方法，对于已经配置为使用Spring的@Transactional注解在事务中运行的测试方法。 从Spring Framework 4.3开始，@BeforeTransaction方法不需要是公共的，可以在基于Java 8的接口默认方法中声明。

以下示例显示如何使用@BeforeTransaction批注：

```java
@BeforeTransaction 
void beforeTransaction() {
    // logic to be executed before a transaction is started
}
```

**@AfterTransaction**

对于已经配置为在事务中使用Spring的@Transactional注解运行的测试方法，@AfterTransaction指示应在事务结束后运行带注解的void方法。 从Spring Framework 4.3开始，@AfterTransaction方法不需要是公共的，可以在基于Java 8的接口默认方法中声明。

```java
@AfterTransaction 
void afterTransaction() {
    // logic to be executed after a transaction has ended
}
```

**@Sql**

@Sql用于注解测试类或测试方法，以配置在集成测试期间针对给定数据库运行的SQL脚本。 以下示例显示了如何使用它：

```java
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) 
public void userTest {
    // execute code that relies on the test schema and test data
}
```

**@SqlConfig**

@SqlConfig定义元数据，用于确定如何解析和运行使用@Sql批注配置的SQL脚本。 以下示例显示了如何使用它：

```java
@Test
@Sql(
    scripts = "/test-user-data.sql",
    config = @SqlConfig(commentPrefix = "`", separator = "@@") 
)
public void userTest {
    // execute code that relies on the test data
}
```

**@SqlGroup**

@SqlGroup是一个容器注解，它聚合了几个@Sql注解。 你可以原生使用@SqlGroup来声明多个嵌套的@Sql注解，或者你可以将它与Java 8对可重复注解的支持结合使用，其中@Sql可以在同一个类或方法上多次声明，隐式生成此容器注解。 以下示例显示如何声明SQL组：

```java
@Test
@SqlGroup({ 
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
public void userTest {
    // execute code that uses the test schema and test data
}
```

### 3.4.2 标准注解支持

对于Spring TestContext Framework的所有配置，标准语义支持以下注解。 请注意，这些注解并非特定于测试，可以在Spring Framework中的任何位置使用。

@Autowired

@Qualifier

@Resource（javax.annotation）如果存在JSR-250

@ManagedBean（javax.annotation）如果存在JSR-250

@Inject（javax.inject）如果存在JSR-330

@Named（javax.inject）如果存在JSR-330

@PersistenceContext（javax.persistence）如果存在JPA

@PersistenceUnit（javax.persistence）如果存在JPA

@Required

@Transactional

> JSR-250生命周期注解
>
> 在Spring TestContext Framework中，你可以在ApplicationContext中配置的任何应用程序组件上使用@PostConstruct和@PreDestroy以及标准语义。 但是，这些生命周期注解在实际测试类中的使用有限。
>
> 如果测试类中的方法使用@PostConstruct注解，则该方法在基础测试框架的任何before方法之前运行（例如，使用JUnit Jupiter的@BeforeEach注解的方法），并且该方法适用于测试类中的每个测试方法。 另一方面，如果测试类中的方法使用@PreDestroy注解，则该方法永远不会运行。 因此，在测试类中，我们建议你使用来自底层测试框架的测试生命周期回调，而不是@PostConstruct和@PreDestroy。

## 3.4.3 Spring JUnit 4 测试注解

仅当与SpringRunner，Spring的JUnit 4规则或Spring的JUnit 4支持类一起使用时，才支持以下注解：

* @IfProfileValue
* @ProfileValueSourceConfiguration
* @Timed
* @Repeat

**@IfProfileValue**

@IfProfileValue表示为特定测试环境启用了带注解的测试。如果配置的ProfileValueSource返回所提供名称的匹配值，则启用测试。否则，测试被禁用，并且有效地被忽略。

你可以在类级别，方法级别或两者中应用@IfProfileValue。 @IfProfileValue的类级别使用优先于该类或其子类中的任何方法的方法级别使用。具体而言，如果在类级别和方法级别启用了测试，则启用测试。缺少@IfProfileValue意味着隐式启用了测试。这类似于JUnit 4的@Ignore注解的语义，除了@Ignore的存在总是禁用测试。

以下示例显示了具有@IfProfileValue批注的测试：

```java
@IfProfileValue(name="java.vendor", value="Oracle Corporation") 
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}
```

或者，你可以使用值列表（使用OR语义）配置@IfProfileValue，以在JUnit 4环境中为测试组实现类似TestNG的支持。 请考虑以下示例：

```java
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) 
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}
```

**@ProfileValueSourceConfiguration**

@ProfileValueSourceConfiguration是一个类级注解，它指定在检索通过@IfProfileValue注解配置的配置文件值时要使用的ProfileValueSource的类型。 如果未为测试声明@ProfileValueSourceConfiguration，则默认使用SystemProfileValueSource。 以下示例显示如何使用@ProfileValueSourceConfiguration：

```java
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) 
public class CustomProfileValueSourceTests {
    // class body...
}
```

**@Timed**

@Timed表示带注解的测试方法必须在指定的时间段内（以毫秒为单位）完成执行。 如果文本执行时间超过指定的时间段，则测试失败。

时间段包括运行测试方法本身，测试的任何重复（参见@Repeat），以及测试夹具的任何设置或拆除。 以下示例显示了如何使用它：

```java
@Timed(millis = 1000) 
public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to execute
}
```

Spring的@Timed注解与JUnit 4的@Test（timeout = ...）支持有不同的语义。 具体来说，由于JUnit 4处理测试执行超时的方式（即，通过在单独的线程中执行测试方法），如果测试时间过长，@Test（timeout = ...）会提前让测试失败。 另一方面，Spring的@Timed没有提前让测试失败，而是在失败之前等待测试完成。

**@Repeat**

@Repeat表示必须重复运行带注解的测试方法。 在注解中指定测试方法的执行次数。

要重复的执行范围包括执行测试方法本身以及测试夹具的任何设置或拆除。 以下示例显示了如何使用@Repeat批注：

```java
@Repeat(10) 
@Test
public void testProcessRepeatedly() {
    // ...
}
```

### 3.4.4 Spring JUnit Jupiter 测试注解

仅当与SpringExtension和JUnit Jupiter（即JUnit 5中的编程模型）结合使用时，才支持以下注解：

* @SpringJUnitConfig
* @SpringJUnitWebConfig
* @EnabledIf
* @DisabledIf

**@SpringJUnitConfig**

@SpringJUnitConfig是一个组合注解，它将来自JUnit Jupiter的@ExtendWith（SpringExtension.class）与来自Spring TestContext Framework的@ContextConfiguration相结合。 它可以在类级别用作@ContextConfiguration的替代品。 关于配置选项，@ContextConfiguration和@SpringJUnitConfig之间的唯一区别是可以使用@SpringJUnitConfig中的value属性声明带注解的类。

以下示例显示如何使用@SpringJUnitConfig批注指定配置类：

```java
@SpringJUnitConfig(TestConfig.class) 
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}
```

以下示例显示如何使用@SpringJUnitConfig批注指定配置文件的位置：

```java
@SpringJUnitConfig(locations = "/test-config.xml") 
class XmlJUnitJupiterSpringTests {
    // class body...
}
```

**@SpringJUnitWebConfig**

@SpringJUnitWebConfig是一个组合注解，它将来自JUnit Jupiter的@ExtendWith（SpringExtension.class）与来自Spring TestContext Framework的@ContextConfiguration和@WebAppConfiguration相结合。 你可以在类级别使用它作为@ContextConfiguration和@WebAppConfiguration的替代品。 关于配置选项，@ContextConfiguration和@SpringJUnitWebConfig之间的唯一区别是你可以使用@SpringJUnitWebConfig中的value属性声明带注解的类。 此外，只能使用@SpringJUnitWebConfig中的resourcePath属性覆盖@WebAppConfiguration中的value属性。

以下示例显示如何使用@SpringJUnitWebConfig批注指定配置类：

```java
@SpringJUnitWebConfig(TestConfig.class) 
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}
```

以下示例显示如何使用@SpringJUnitWebConfig批注指定配置文件的位置：

```java
@SpringJUnitWebConfig(locations = "/test-config.xml") 
class XmlJUnitJupiterSpringWebTests {
    // class body...
}
```

**@EnabledIf**

@EnabledIf用于表示已启用带注解的JUnit Jupiter测试类或测试方法，并且如果提供的表达式的计算结果为true，则应运行该函数。 具体来说，如果表达式求值为Boolean.TRUE或字符串等于true（忽略大小写），则启用测试。 在类级别应用时，默认情况下也会自动启用该类中的所有测试方法。

表达式可以是以下任何一种：

* Spring Expression Language（SpEL）表达式。 例如：@EnabledIf("#{systemProperties\['os.name'].toLowerCase().contains('mac')}")
* Spring环境中可用的属性的占位符。 例如：@EnabledIf("${smoke.tests.enabled}")
* 文字文字。 例如：@EnabledIf("true")

但请注意，不是属性占位符动态解析结果的文本文字的实用价值为零，因为@EnabledIf（“false”）等同于@Disabled而@EnabledIf（“true”）在逻辑上毫无意义。

你可以使用@EnabledIf作为元注解来创建自定义组合注解。 例如，你可以创建自定义@EnabledOnMac注解，如下所示：

```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
```

**@DisabledIf**

@DisabledIf用于表示已禁用带注解的JUnit Jupiter测试类或测试方法，并且如果提供的表达式的计算结果为true，则不应执行。 具体来说，如果表达式求值为Boolean.TRUE或字符串等于true（忽略大小写），则禁用测试。 在类级别应用时，该类中的所有测试方法也会自动禁用。

表达式可以是如下格式：

* Spring Expression Language（SpEL）表达式。 例如：@DisabledIf("#{systemProperties\['os.name'].toLowerCase().contains('mac')}")
* Spring环境中可用的属性的占位符。 例如： @DisabledIf("${smoke.tests.disabled}")
* 文字文字。 例如：@DisabledIf（“true”）

但请注意，不是属性占位符动态解析结果的文本文字实际值为零，因为@DisabledIf（“true”）等同于@Disabled，而@DisabledIf（“false”）在逻辑上没有意义。

你可以使用@DisabledIf作为元注解来创建自定义组合注解。 例如，你可以创建自定义@DisabledOnMac注解，如下所示：

```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
```

### 3.4.5 测试的元注解支持

你可以将大多数与测试相关的注解用作元注解，以创建自定义组合注解并减少测试套件中的配置重复。

你可以将以下各项作为元注解与TestContext框架结合使用。

* @BootstrapWith
* @ContextConfiguration
* @ContextHierarchy
* @ActiveProfiles
* @TestPropertySource
* @DirtiesContext
* @WebAppConfiguration
* @TestExecutionListeners
* @Transactional
* @BeforeTransaction
* @AfterTransaction
* @Commit
* @Rollback
* @sql
* @SqlConfig
* @SqlGroup
* @Repeat（仅在JUnit 4上受支持）
* @Timed（仅在JUnit 4上受支持）
* @IfProfileValue（仅在JUnit 4上受支持）
* @ProfileValueSourceConfiguration（仅在JUnit 4上受支持）
* @SpringJUnitConfig（仅在JUnit Jupiter上受支持）
* @SpringJUnitWebConfig（仅在JUnit Jupiter上受支持）
* @EnabledIf（仅在JUnit Jupiter上受支持）
* @DisabledIf（仅在JUnit Jupiter上受支持）

请考虑以下示例：

```java
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
```

如果我们发现我们在基于JUnit 4的测试套件中重复了前面的配置，我们可以通过引入一个自定义组合注解来集中Spring的常用测试配置来减少重复，如下所示：

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
```

然后我们可以使用我们的自定义@TransactionalDevTestConfig注解来简化各个基于JUnit 4的测试类的配置，如下所示：

```java
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
```

如果我们编写使用JUnit Jupiter的测试，我们可以进一步减少代码重复，因为JUnit 5中的注解也可以用作元注解。 请考虑以下示例：

```java
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
```

如果我们发现我们在基于JUnit Jupiter的测试套件中重复上述配置，我们可以通过引入一个自定义组合注解来集中Spring和JUnit Jupiter的常用测试配置来减少重复，如下所示：

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
```

然后我们可以使用我们的自定义@TransactionalDevTestConfig注解来简化各个基于JUnit Jupiter的测试类的配置，如下所示：

```java
@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }
```

由于JUnit Jupiter支持使用@Test，@RepeatedTest，ParameterizedTest和其他作为元注解，因此你还可以在测试方法级别创建自定义组合注解。 例如，如果我们希望创建一个组合注解，它将来自JUnit Jupiter的@Test和@Tag注解与Spring中的@Transactional注解相结合，我们可以创建一个@TransactionalIntegrationTest注解，如下所示：

```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
```

然后我们可以使用我们的自定义@TransactionalIntegrationTest批注来简化各个基于JUnit Jupiter的测试方法的配置，如下所示：

```java
@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }
```
