更新时间:2024-02-05 15:11:12作者:佚名
【注】本文译自:withBootand@-
使用@注解,Boot提供了一种便捷的方式来启动要在测试中使用的应用程序上下文。在本教程中,我们将讨论何时使用@以及何时更好地使用其他工具进行测试。我们还将研究自定义应用程序上下文的不同方式以及怎么减小测试运行时间。
代码示例
本文附有上的工作代码示例。
“使用Boot进行测试”系列
本教程是系列的一部份:
使用Boot进行单元测试
集成测试与单元测试
在开始使用Boot进行集成测试之前,让我们定义集成测试与单元测试的区别。
单元测试囊括单个“单元”test是什么意思,其中一个单元一般是单个类,但也可以是组合测试的一组内降维。
集成测试可以是以下任何一项:
Boot提供了@注解,我们可以使用它来创建一个应用程序上下文,其中包含我们对上述所有测试类型所需的所有对象。并且请注意,过度使用@可能会造成测试套件运行时间特别长。
因而,对于囊括多个单元的简单测试,我们应当创建简单的测试,与单元测试十分相像,在单元测试中,我们自动创建测试所需的对象图并模拟其余部份。这样,不会在每次测试开始时启动整个应用程序上下文。
测试切块
我们可以将我们的Boot应用程序作为一个整体来测试、一个单元一个单元地测试、也可以一层一层地测试。使用Boot的测试切块注解,我们可以分别测试每一层。
在我们详尽研究@注解之前,让我们探求一下测试切块注解,以检测@是否真的是您想要的。
@注解加载完整的应用程序上下文。相比之下,测试切块注释仅加载测试特定层所需的bean。正由于这般,我们可以防止何必要的模拟和副作用。
@
我们的Web控制器承当许多职责,比如侦听HTTP恳求、验证输入、调用业务逻辑、序列化输出以及将异常转换为正确的响应。我们应当编撰测试来验证所有这种功能。
@测试切块注释将使用恰好足够的组件和配置来设置我们的应用程序上下文,以测试我们的Web控制器层。比如,它将设置我们的@、@、一个bean和其他一些手动配置。
@
@用于测试控制器。@的工作方法类似于@注释,不同之处在于它不是WebMVC组件和配置,而是启动组件和配置。其中一个bean是,我们可以使用它来测试我们的端点。
@
如同@容许我们测试我们的web层一样,@用于测试持久层。
它配置我们的实体、存储库并设置嵌入式数据库。如今,这一切都挺好,然而,测试我们的持久层意味着哪些?我们到底在测试哪些?假如查询,这么哪些样的查询?要找出所有这种问题的答案,请阅读我关于使用Boot和@测试JPA查询的文章。
@
DataJDBC是Data系列的另一个成员。假如我们正在使用这个项目而且想要测试持久层,这么我们可以使用@注解。@会手动为我们配置在我们的项目中定义的嵌入式测试数据库和JDBC储存库。
另一个类似的项目是JDBC,它为我们提供了对象来执行直接查询。@注解手动配置测试我们的JDBC查询所需的对象。
依赖
本文中的代码示例只须要依赖Boot的test和JUnit:
dependencies {
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.junit.jupiter:junit-jupiter:5.4.0')
}
使用@创建
@在默认情况下开始在测试类的当前包中搜索,之后在包结构中向下搜索,找寻用@ion注解的类,之后从中读取配置以创建应用程序上下文。这个类一般是我们的主要应用程序类,由于@n注解包括@ion注解。之后,它会创建一个与在生产环境中启动的应用程序上下文十分相像的应用程序上下文。
我们可以通过许多不同的形式自定义此应用程序上下文,如下一节所述。
由于我们有一个完整的应用程序上下文,包括web控制器、数据储存库和数据源,@对于贯串应用程序所有层的集成测试十分便捷:
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Test
void registrationWorksThroughAllLayers() throws Exception {
UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net");
mockMvc.perform(post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
UserEntity userEntity = userRepository.findByName("Zaphod");
assertThat(userEntity.getEmail()).isEqualTo("zaphod@galaxy.net");
}
}
@
本教程中的代码示例使用@注解告诉JUnit5启用支持。从Boot2.1开始,我们不再须要加载,由于它作为元注释包含在Boot测试注释中,比如@、@和@。
在这儿,我们另外使用@将实例添加到应用程序上下文中。
我们使用这个对象向我们的应用程序执行POST恳求并验证它是否按预期响应。
之后,我们使用应用程序上下文中的来验证恳求是否造成数据库状态发生预期的变化。
自定义应用程序上下文
我们可以有好多种方式来自定义@创建的应用程序上下文。让我们瞧瞧我们有什么选择。
自定义应用上下文时的注意事项
应用程序上下文的每位自定义都是使其与在生产设置中启动的“真实”应用程序上下文不同的另一件事。为此,为了使我们的测试尽可能接近生产,我们应当只订制让测试运行真正须要的东西!
添加手动配置
在里面,我们早已听到了手动配置的作用:
@SpringBootTest
@AutoConfigureMockMvc
class RegisterUseCaseIntegrationTest {
...
}
:还有好多其他可用的手动配置,每位都可以将其他bean添加到应用程序上下文中。以下是文档中其他一些有用的内容:
设置自定义配置属性
一般,在测试中须要将一些配置属性设置为与生产设置中的值不同的值:
@SpringBootTest(properties = "foo=bar")
class SpringBootPropertiesTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
假如属性foo存在于默认设置中,它将被此测试的值bar覆盖。
使用@外部化属性
假如我们的许多测试须要相同的属性集,我们可以创建一个配置文件-
.或-
.yml并通过激活某个配置文件从该文件加载属性:
# application-test.yml foo: bar
@SpringBootTest
@ActiveProfiles("test")
class SpringBootProfileTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
使用@设置自定义属性
另一种订制整个属性集的方式是使用@注释:
# src/test/resources/foo.properties
foo=bar
@SpringBootTest
@TestPropertySource(locations = "/foo.properties")
class SpringBootPropertySourceTest {
@Value("${foo}")
String foo;
@Test
void test(){
assertThat(foo).isEqualTo("bar");
}
}
foo.文件中的所有属性都加载到应用程序上下文中。@还可以配置更多。
使用@注入模拟
假如我们只想测试应用程序的某个部份而不是从传入恳求到数据库的整个路径,我们可以使用@替换应用程序上下文中的个别bean:
@SpringBootTest
class MockBeanTest {
@MockBean
private UserRepository userRepository;
@Autowired
private RegisterUseCase registerUseCase;
@Test
void testRegister(){
// given
User user = new User("Zaphod", "zaphod@galaxy.net");
boolean sendWelcomeMail = true;
given(userRepository.save(any(UserEntity.class))).willReturn(userEntity(1L));
// when
Long userId = registerUseCase.registerUser(user, sendWelcomeMail);
// then
assertThat(userId).isEqualTo(1L);
}
}
在这些情况下,我们用模拟替换了bean。使用的given方式,我们指定了此模拟的预期行为,以测试使用此储存库的类。
您可以在我关于模拟的文章中阅读有关@注解的更多信息。
使用@添加Bean
假如个别bean未包含在默认应用程序上下文中,但我们在测试中须要它们,我们可以使用@注解导出它们:
package other.namespace;@Componentpublic class Foo {}
@SpringBootTest
@Import(other.namespace.Foo.class)
class SpringBootImportTest {
@Autowired
Foo foo;
@Test
void test() {
assertThat(foo).isNotNull();
}
}
默认情况下,Boot应用程序包含它在其包和子包中找到的所有组件,因而一般只有在我们想要包含其他包中的bean时才须要这样做。
使用@覆盖Bean
使用@,我们除了可以包含测试所需的其他bean,还可以覆盖应用程序中早已定义的bean。在我们关于使用@进行测试的文章中阅读更多相关信息。
创建自定义@n
我们甚至可以创建一个完整的自定义Boot应用程序来启动测试。假如这个应用程序类与真正的应用程序类在同一个包中,并且在测试源而不是生产源中,@会在实际应用程序类之前找到它,并从这个应用程序加载应用程序上下文。
或则,我们可以告诉Boot使用那个应用程序类来创建应用程序上下文:
@SpringBootTest(classes = CustomApplication.class)
class CustomApplicationTest {
}
然而,在执行此操作时,我们正在测试可能与生产环境完全不同的应用程序上下文网校头条,因而仅当未能在测试环境中启动生产应用程序时,这才应当是最后的手段。并且test是什么意思,一般有更好的方式,比如使真实的应用程序上下文可配置以排除不会在测试环境中启动的bean。让我们看一个反例。
假定我们在应用程序类上使用@注解。每次启动应用程序上下文时(虽然在测试中),所有@作业都将启动,而且可能与我们的测试冲突。我们一般不希望作业在测试中运行,因而我们可以创建第二个没有@注释的应用程序类,并在测试中使用它。并且,更好的解决方案是创建一个可以使用属性切换的配置类:
@Configuration
@EnableScheduling
@ConditionalOnProperty(
name = "io.reflectoring.scheduling.enabled",
havingValue = "true",
matchIfMissing = true)
public class SchedulingConfiguration {
}
我们已将@注解从我们的应用程序类移到这个特殊的配置类。将属性
io...设置为false将造成这种不会作为应用程序上下文的一部份加载:
@SpringBootTest(properties = "io.reflectoring.scheduling.enabled=false")
class SchedulingTest {
@Autowired(required = false)
private SchedulingConfiguration schedulingConfiguration;
@Test
void test() {
assertThat(schedulingConfiguration).isNull();
}
}
我们如今已然成功地停用了测试中的预定作业。属性
io...可以通过上述任何方式指定。
为何我的集成测试那么慢?
包含大量@注释测试的代码库可能须要相当长的时间就能运行。的测试支持足够智能,只创建一次应用上下文并在后续测试中重复使用,而且假如不同的测试须要不同的应用上下文,它依然会为每位测试创建一个单独的上下文,这须要一些时间来完成每位测试。
前面描述的所有自定义选项就会造成创建一个新的应用程序上下文。为此,我们可能希望创建一个配置并将其用于所有测试,便于可以重用应用程序上下文。
倘若您对测试耗费在设置和应用程序上下文上的时间感兴趣,您可能须要查看JUnit,它可以包含在或Maven建立中,以生成关于JUnit5怎么耗费时间的挺好的报告。
推论
.@是一种为测试设置应用程序上下文的特别便捷的方式,它特别接近我们将在生产中使用的上下文。有很多选项可以自定义此应用程序上下文,但应慎重使用它们,由于我们希望我们的测试尽可能接近生产运行。
假如我们想在整个应用程序中进行测试,@会带来最大的价值。为了仅测试应用程序的个别切块或层,我们还有其他选项可用。
本文中使用的示例代码可在上找到。