2024软件测试面试刷题,这个小程序(永久刷题),靠它快速找到工作了!(刷题APP的天花板)-CSDN博客文章浏览阅读846次,点赞38次,收藏6次。你知不知道有这么一个软件测试面试的刷题小程序。里面包含了面试常问的软件测试基础题,web自动化测试、app自动化测试、接口测试、性能测试、自动化测试、安全测试及一些常问到的人力资源题目。最主要的是他还收集了像阿里、华为这样的大厂面试真题,还有互动交流板块……https://blog.csdn.net/AI_Green/article/details/134931243?spm=1001.2014.3001.5501揭开增强Spring Boot测试能力的秘密!探索我们如何利用特定技术,将测试运行时间缩短60%!
以下为作者观点:
嘿,朋友们!让我们一起进入使用 JUnit 进行 Spring Boot 测试的精彩世界。它的功能非常强大,为我们的代码测试提供了一个真实的环境。但是,如果我们不对测试进行优化,它们可能会很慢,并对团队的变更时间产生负面影响。
本文将分享如何优化 Spring Boot 测试,使其更快、更高效、更可靠。想象一下,一个APP的测试需要10分钟才能执行,这个时间也不少!让我们看看如何在短时间内快速完成这些测试吧!
了解Spring中的测试分片(Test Slicing)
Spring 中的测试分片(Test Slicing)允许测试应用程序的特定部分,只关注相关组件,而不是加载整个上下文。它是通过 @WebMvcTest、@DataJpaTest 或 @JsonTest 等注解实现的。这些注解是一种有针对性的方法,可将上下文加载限制在特定层或技术上。例如,@WebMvcTest 主要加载 Web 层,而 @DataJpaTest 则初始化数据 JPA 层,以实现更简洁高效的测试。这种选择性加载方法是优化测试效率的基石。
还有更多注解可用于切分上下文。请参阅有关测试切片的 Spring 官方文档。(https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration.slices)
测试切片:使用 @DataJpaTest 代替 @SpringBootTest
让我们来看一个示例(代码如下)。测试首先删除目标表中的所有数据(货物和集装箱,每个货物可以有多个集装箱),然后保存新的货物。然后,创建一个包含 50 个线程的线程池,每个线程调用 svc.createOrUpdateContainer 方法。
测试将等待所有线程结束,然后检查数据库是否只有一个容器。
该测试主要是检查并发问题,涉及大量线程,在我的机器上耗时约 16 秒--对于单个服务检查来说,这可是一大块时间,不是吗?
@ActiveProfiles("test")@SpringBootTestabstractclassBaseIT { @Autowired privatelateinitvarshipmentRepo: ShipmentRepository @Autowired privatelateinitvarcontainerRepo: ContainerRepository}classContainerServiceTest : BaseIT() { @Autowired privatelateinitvarsvc: ContainerService @BeforeEach funsetup() { shipmentRepo.deleteAll() containerRepo.deleteAll() shipmentRepo.save(shipment) } @Test funtestConcurrentUpdatesForContainer() { valexecutor= Executors.newFixedThreadPool(50) repeat(50) { executor.execute { containerService.createOrUpdateContainer("${shipment.id}${svc.DEFAULT_CONTAINER}", Patch("NEW_LABEL")) } } executor.shutdown() while (!executor.awaitTermination(100, TimeUnit.MILLISECONDS)) { // busy waiting for executor to terminate } assertThat(containerRepo.find(shipment)).hasSize(1) }}
我们遇到的第一个问题是类声明:
class ContainerServiceTest : BaseIT()
问题的起因是 BaseIT 类使用了 @SpringBootTest。这会导致整个应用程序的 Spring 上下文被加载(每次我们都会使用上下文缓存机制,这个我们稍后再谈!)。当应用程序足够大时,大量的 Bean 就会被加载--对于具有特定目标的测试来说,这是一项代价高昂的操作。
但是,我们并不想加载所有内容。我们只需要加载 ContainerService Bean 和 JPA 资源库。我们可以改用 @DataJpaTest。该注解只加载应用程序的 JPA 部分,而这正是我们本次测试所需要的。让我们试试看!
@DataJpaTestclass ContainerServiceTest { @Autowired private lateinit var svc: ContainerService @Autowired private lateinit var shipmentRepo: ShipmentRepository @Autowired private lateinit var containerRepo: ContainerRepository}
执行时会出现异常:
org.springframework.beans.factory.BeanCreationException: Failed to replace DataSource with an embedded database for tests. If you want an embedded database please put a supported one on the classpath or tune the replace attribute of @AutoConfigureTestDatabase.
@DataJpaTest 有一个注解 @AutoConfigureTestDatabase,默认情况下,它会为测试设置一个 H2 内存数据库,并配置 DataSource 以使用它。然而,在这种情况下,classpath 中找不到 H2 依赖项。
实际上,我们并不想在测试中使用 H2,所以我们可以告诉 @AutoConfigureTestDatabase 不要用 H2 替换我们配置的数据库。此外,我们还必须配置和加载我们自己的数据库,这里通过导入名为 EmbeddedDataSourceConfig 的 @Configuration 类来实现(它只是创建了一个 DataSource 类型的 @Bean)。
@DataJpaTest@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)@Import(EmbeddedDataSourceConfig::class) // Import the embedded database configuration if needed.@ActiveProfiles("test") // Use the test profile to load a different configuration for tests.class ContainerServiceTest { // test code}
让我们再次尝试运行测试。现在,它失败了,出现了以下错误:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ContainerServiceTest': Unsatisfied dependency expressed through field 'containerService'
你已经掌握了诀窍,需要在 Spring 上下文中加载 ContainerService Bean!
@DataJpaTest@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)@Import(ContainerService::class, EmbeddedDataSourceConfig::class)@ActiveProfiles("test")class ContainerServiceTest { // test code}
Uh-oh! Spring 上下文加载成功,但测试却失败了,错误如下:
java.lang.AssertionError:
Expected size:<1> but was:<0> in:
<[]>
如果查看 @DataJpaTest,你会发现它使用了 @Transactional 注解。这意味着,默认情况下,从目标表中删除数据和创建新容器只会在测试方法结束时提交,因此线程创建的事务看不到这些更改。
由于我们希望在主事务(@DataJpaTest 使用的事务)中提交事务,因此需要使用 Propagation.REQUIRES_NEW:
@DataJpaTest@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)@Import(ContainerService::class, EmbeddedDataSourceConfig::class)@ActiveProfiles("test")class ContainerServiceTest { @Autowired private lateinit var transactionTemplate: TransactionTemplate @Autowired private lateinit var svc: ContainerService @Autowired private lateinit var shipmentRepo: ShipmentRepository @Autowired private lateinit var containerRepo: ContainerRepository @BeforeEach fun setup() { transactionTemplate.propagationBehavior = TransactionTemplate.PROPAGATION_REQUIRES_NEW transactionTemplate.execute { shipmentRepo.deleteAll() containerRepo.deleteAll() shipmentRepo.save(shipment) } }}
测试通过,仅用 8 秒(加载上下文 + 运行)就完成了,比以前快了一倍!
测试切片:@JsonTest 精确验证 JSON 序列化/解序列化
请看这个测试片段:
public class EventDeserializationIT extends BaseIT { private static final String RESOURCE_PATH = "event-example.json"; @Autowired private ObjectMapper objectMapper; private Event dto; @Test public void testDeserialization() throws Exception { String json = Resources.toString(Resources.getResource(RESOURCE_PATH), UTF_8); dto = objectMapper.reader().forType(Event.class).readValue(json); assertThat(dto.getData().getNewTour().getFromLocation()).isNotNull(); assertThat(dto.getData().getNewTour().getToLocation()).isNotNull(); }}
该测试的目的是确保正确的反序列化。我们可以使用 @JsonTest 注解导入测试中所需的 Bean。我们只需要对象映射器,无需扩展任何其他类!使用此注解只会应用与 JSON 测试相关的配置(即 @JsonComponent、Jackson Module)。
@JsonTestpublic class EventDeserializationTest { @Autowired private ObjectMapper objectMapper; // Test implementation}
测试切片:用于 REST API 的 @WebMvcTest
使用 @WebMvcTest,我们可以在不启动服务器(如嵌入式 Tomcat)或加载整个应用程序上下文的情况下测试 REST API。这一切都以特定控制器为目标。快速高效,就是这么简单!
@WebMvcTest(ShipmentServiceController.class)public class ShipmentServiceControllerTests { @Autowired private MockMvc mvc; @MockBean private ShipmentService service; @Test public void getShipmentShouldReturnShipmentDetails() { given(this.service.schedule(any())).willReturn(new LocalDate()); this.mvc.perform( get("/shipments/12345") .accept(MediaType.APPLICATION_JSON) .andExpect(status().isOk()) .andExpect(jsonPath("$.number").value("12345")) // ... ); }}
驯服 Mock/Spy Beans 和上下文缓存难题
让我们深入了解 Spring Test 上下文缓存机制的复杂性!
当你的测试涉及 Spring Test 功能(如 @SpringBootTest、@WebMvcTest、@DataJpaTest)时,它们需要一个正在运行的 Spring 上下文。为测试启动 Spring 上下文需要大量时间,尤其是在使用 @SpringBootTest 填充整个上下文的情况下,如果每个测试都启动自己的上下文,就会导致测试执行开销增加,构建时间延长。
幸运的是,Spring Test 提供了一种机制来缓存已启动的应用程序上下文,并将其重新用于具有类似上下文需求的后续测试。
缓存就像一个地图,有一定的容量。映射键由几个参数计算得出,其中包括加载到上下文中的 Bean。
包括:
-
位置 (from @ContextConfiguration)
-
类 (from @ContextConfiguration)
-
contextInitializerClasses (from @ContextConfiguration)
-
contextCustomizers (from ContextCustomizerFactory) – 这包括 @DynamicPropertySource 方法以及 Spring Boot 测试支持的各种功能,如 @MockBean 和 @SpyBean
-
contextLoader (from @ContextConfiguration)
-
parent (from @ContextHierarchy)
-
activeProfiles (from @ActiveProfiles)
-
propertySourceLocations (from @TestPropertySource)
-
propertySourceProperties (from @TestPropertySource)
-
resourceBasePath (from @WebAppConfiguration)
例如,如果 TestClassA 为 @ContextConfiguration 的 locations(或值)属性指定了 {"app-config.xml"、"test-config.xml"},TestContext 框架就会加载相应的 ApplicationContext,并将其存储在静态上下文缓存中,该缓存的键完全基于这些位置。因此,如果 TestClassB 也为其位置定义了 {"app-config.xml"、"test-config.xml"}(通过显式继承或隐式继承),并且没有为上述任何其他属性定义不同的属性,那么两个测试类将共享同一个 ApplicationContext。这意味着加载应用程序上下文的设置成本只需一次(每个测试套件),而且后续测试执行速度会更快。
如果在不同的测试中使用不同的属性,例如在测试中使用不同的(ContextConfiguration、TestPropertySource、@MockBean 或 @SpyBean),缓存键就会发生变化。对于每个新上下文(缓存中不存在),都必须从头开始加载。
如果有许多不同的上下文,缓存中的旧键就会被移除,因此下一个运行的测试可能会使用这些缓存上下文,就需要重新加载它们。这将导致额外的测试时间。
一种效率优化方法是将 mock Bean 合并到父类中。这可以确保上下文保持不变,从而提高效率并避免多次重新加载上下文。
前后示例:
@SpringBootTestpublic class TestClass1 { @MockBean private DependencyA dependencyA; // Test implementation}@SpringBootTestpublic class TestClass2 { @MockBean private DependencyB dependencyB; // Test implementation}@SpringBootTestpublic class TestClass3 { @MockBean private DependencyC dependencyC; // Test implementation}
如果我们尝试运行上述示例,上下文将被重新加载3次,这一点也不高效。让我们试着优化一下。
@SpringBootTestpublic abstract class BaseTestClass { @MockBean private DependencyA dependencyA; @MockBean private DependencyB dependencyB; @MockBean private DependencyC dependencyC;}// Extend the BaseTestClass for each test classpublic class TestClass1 extends BaseTestClass { @Test public void testSomething1() { // Test implementation }}public class TestClass2 extends BaseTestClass { @Test public void testSomething2() { // Test implementation }}public class TestClass3 extends BaseTestClass { @Test public void testSomething3() { // Test implementation }}
现在,上下文只需重新加载一次,效率更高!
或者更好:使用 @Import 注解导入包含 mock Bean 的配置类,可以避免类继承。
@TestConfigurationclass Config { @MockBean private DependencyA dependencyA; @MockBean private DependencyB dependencyB; @MockBean private DependencyC dependencyC;}@Import(Config::class)@ActiveProfiles("test")class TestClass1 { // Test code}
使用 @DirtiesContext 之前请三思
在测试类中应用 @DirtiesContext 会在测试执行后删除应用程序上下文。这会将 Spring 上下文标记为脏上下文,从而阻止 Spring Test 重用它。谨慎考虑使用此注解很重要。
虽然有些人使用它来重置数据库中的 ID,但还有更好的替代方法。例如,@Transactional 注解可用于在执行测试后回滚事务。
并行执行测试
默认情况下,JUnit Jupiter 测试在单线程中顺序运行。然而,JUnit 5.3 中引入了一项可选功能,即允许测试并行运行,以加快执行速度。🚀
要启动并行测试执行,请按以下步骤操作:
1.在 test/resources 中创建一个 junit-platform.properties 文件。
2.在该文件中添加以下配置:junit.jupiter.execution.parallel.enabled = true
3.在要并行运行的每个类中添加以下内容。@Execution(CONCURRENT)
请记住,某些测试可能因其性质而与并行执行不兼容。在这种情况下,不应添加 @Execution(CONCURRENT)。有关不同执行模式的更多解释,请参阅 JUnit: writing tests - parallel execution。(https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution)
结果
应用上述所有优化措施后,我们的 CI/CD 管道有了很大改观。我们的测试速度更快了,现在只需要4分15秒,而以前需要10分7秒,提高了60%!
结论
在这次优化 Spring Boot 测试的冒险中,我们利用了一系列策略来提高测试效率和速度。让我们总结一下我们实施的策略:
-
测试切片:利用 @WebMvcTest、@DataJpaTest 和 @JsonTest 将测试重点放在特定层或组件上。你可以查看更多信息(测试 Spring Boot 应用程序)。(https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications)
-
上下文缓存困境:通过优化mock和spy Bean的使用,克服与脏 ApplicationContext 缓存相关的难题。请参见Spring测试上下文缓存。(https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/caching.html)
-
并行测试执行:启用并行测试执行以大幅缩短测试套件的执行时间。请参阅《JUnit 5并行执行用户指南》。(https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution)
这些策略共同将测试转化为一个更快、更可靠和更高效的过程。每种策略无论是单独使用还是结合使用,都能极大地促进测试实践的优化,使工程师能以更高的效率交付更高质量的软件。
行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 786229024,里面有各种测试开发资料和技术可以一起交流哦。
最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。