学习如何有效地测试Spring Boot应用程序可能是一个坎儿,特别是对于新手来说。如果不了解Spring的依赖注入机制以及Spring Boot的自动配置的原理,我们最终可能会向我们的测试抛出注释。试图使事情顺利进行。虽然这种尝试和错误可能会解决某些情况下的测试设置,但结果通常是一个不太理想的测试设置。通过这篇博文,我收集了我在项目中和回答问题时 在Stack Overflow 看到的关于测试Spring Boot应用程序的最常见坑。
第一个陷阱是mocking collaborators,也就是我们被测试的类所依赖的对象。如果我们已经熟悉了Mockito,我们可能知道我们可以使用 @Mock 来为我们的单元测试创建模拟。
@Mock
在为我们的Spring Boot应用程序编写测试时,我们不需要再去学习任何特定的Mockito知识。然而,我们必须意识到我们正在编写什么样的测试。我们的测试是在有或没有Spring TestContext的情况下工作吗?
这很重要,因为它决定了是否要使用 @Mock vs. @MockBean 。虽然这两个注解都为我们的合作者创建了一个模拟的版本,但 @Mock 只与没有Spring TestContext的普通单元测试有关。在这种情况下,我们通常创建合作者的模拟,并通过被测类的公共构造函数注入它们。
对于使用Spring TestContext的测试,例如使用 Spring Boot Test slice annotation 或 @SpringBootTest 时,事情会有所不同。在这里,我们仍然要模拟被测类的合作者。但这一次,Spring会组装我们所有的Bean并执行依赖性注入。因此,我们必须在Spring TestContext中替换(或添加)一个模拟的协作器版本,作为一个bean。
这就是 @MockBean 发挥作用的地方。我们在测试字段上使用它来指示Spring Test在我们的TestContext中添加这个bean的模拟版本。
@MockBean
无论我们使用 @Mock 还是 @MockBean ,Mockito的存根设置对两者都是一样的。
这里的隐患在于,要么在同一个测试中混合使用这两个注解,要么将其中一个注解用于错误的目的。
我已经在一篇单独的 @Mock vs. @MockBean 博文中介绍了两种注解的比较以及何时使用它们。
在开始测试Spring Boot应用程序时,我们很快就会偶然发现 @SpringBootTest 注解。Spring Boot甚至为从 start.spring.io 生成的每个新项目创建了一个使用该注解的基本测试。
@SpringBootTest
该注解的名称可能意味着每个Spring Boot测试都要使用和要求它。但事实并非如此。
只要我们想写一个与整个Spring Context一起工作的集成测试,我们就使用 @SpringBootTest 。Spring Test将为我们创建一个TestContext,其中包含我们所有的Bean( @Component , @Configuration , @Service ,等等)。
@Component
@Configuration
@Service
这意味着我们还必须提供我们要连接的每一个外部基础设施组件。想象一下,我们正在编写一个连接到数据库的CRUD应用程序。如果在测试执行期间没有数据库可以连接,我们将无法创建和使用我们的存储库类。
如果我们只使用 @SpringBootTest 进行测试,我们很快就会发现我们的测试套件比普通的JUnit和Mockito测试花费的时间更长。启动Spring Context会导致测试执行时间变慢,因为所有东西都必须被实例化和初始化。
作为一个普遍的建议,我们应该尽量在较低的测试级别上测试和验证我们的实现。这意味着我们的 @Service 类中的一个特定的if-block可以通过单元测试来测试。此外,我们可以通过使用 @WebMvcTest 来确保我们的Spring安全配置是有效的。
@WebMvcTest
对于验证多个组件的交互的集成测试,或者在编写端到端测试时, @SpringBootTest 就可以发挥作用。
让自己熟悉 不同的Spring Boot测试切片注释 。此外,还有 进一步调整@SpringBootTest的几种方法 ,我们必须要注意。
我想这个陷阱是不用说的。如果我们不测试我们的代码,我们怎么能说它在工作呢?
虽然我们可能已经手动检查了我们的实现,但我们怎么能确保任何即将到来的变化不会破坏我们的功能?
如果我们不测试我们的应用程序,我们的用户肯定会的,如果他们发现有一半支持的功能,他们不会感到高兴。
在学习Spring Boot时,测试可能不是首要任务。这很好,只要我们确保在对框架感到满意时,尽快回到测试的话题上。
我们是在实现之前(也就是测试驱动开发)还是在实现之后写测试,取决于个人的喜好。我有一个很好的经验,先写测试,导致更周到的设计和更小的步骤。
反过来做,在我们完成实现后马上给我们的代码添加测试,通常会导致*不那么好的测试。我们已经知道了实现的样子,并偏向于只测试最基本的东西。除此之外,我们可能已经很晚才整合我们的变化,因此,没有什么时间来彻底测试实现。
Spring框架和Spring Boot强调了测试的重要性,并通过强大的测试支持和工具鼓励我们编写测试。
测试是每个Spring Boot项目的重要组成部分,因为每个新项目都已经有了基本的集成测试和 测试瑞士军刀 。如果我们删除(或禁用)这个自动生成的测试, Josh Long 将亲自拜访我们。
从字面上看,没有任何借口不写测试–除了我们不知道 如何 (还没有)。但这一点我们可以轻松解决。
这个博客上有很多 实践测试建议 。从下面的【Spring Boot单元和集成测试概述】( Spring Boot Unit and Integration Testing Overview - rieckpil )开始。接下来,考虑报名参加 测试Spring Boot应用程序入门 ,以启动你的Spring Boot测试成功。
这与第二个陷阱(广泛使用 @SpringBootTest )相联系。
为每一个测试类启动一个新的Spring TestContext是很昂贵的。那么为什么不缓存一个已经启动的Spring TestContext呢?
这正是Spring Test为我们做的事情
每当我们要启动一个新的Spring TestContext、一个切片或整个上下文时,Spring会考虑这个测试的一个已经启动的上下文。如果现有的上下文与我们即将运行的测试类的上下文配置相匹配,Spring将重新使用该上下文。
如果没有合适的已经启动的缓存上下文(说是缓存缺失),Spring就会启动一个新的,并在之后存储该上下文,以便进一步重用其他测试。
那么Spring是如何确定一个上下文是否可以被重用的,以及我们如何有效地使用这个功能呢?
想象一下,一个集成测试激活了配置文件 integration-test ,而另一个测试激活了 web-test 。在这种情况下,Spring不会重复使用同一个上下文,因为由于配置文件不同,我们的配置看起来完全不同。
integration-test
web-test
有十多个配置和设置值决定了一个缓存的唯一性。为了有效地利用这一性能改进,我们必须将大部分的Spring TestContext设置对齐。我们应该避免多个上下文配置,尤其是对那些与整个 ApplicationContext 一起工作的测试。
ApplicationContext
在我的一个项目中,我将整个构建时间(运行 mvn verify )从25分钟减少到9分钟,同时充分利用了Spring TestContext Caching机制。我通过调整昂贵的集成测试的上下文配置来做到这一点。
mvn verify
让自己熟悉几个配置值以及如何充分利用Spring的 TestContext缓存机制 。
在测试Spring Boot应用程序时,另一个导致奇怪测试结果的常见陷阱是将JUnit 4和JUnit 5混在同一个测试类中。
在Stack Overflow上回答问题时,我看到围绕这个话题有很多困惑。
虽然JUnit 5的第一个版本是在2017年发布的,但仍有项目在使用前身(这很好)。由于JUnit 5支持在JUnit 5测试旁边运行JUnit 4测试,我们可以在迁移/过渡期间为我们的项目混合使用这两种测试。
使用JUnit 4和JUnit 5(准确地说,是JUnit Jupiter)的注解和API是行不通的。它是非此即彼的。虽然我们可以在项目中同时拥有JUnit 4和JUnit 5的测试,但由于JUnit Vintage引擎,一个测试类应该选择加入版本4或5。
在 "MyOrderTest "中使用JUnit 4,在 "MyPricingServiceTest "中使用JUnit 5是完全可以的。陷阱在于在同一个测试中混合两个版本的API和注解。
有一些 工具 和 指南 可以用来开始迁移,并帮助转换低垂的果实。当涉及到迁移自定义的 Runner'或 Rule’类时,仍有一些手动工作。
Runner'或
一旦完成向JUnit 5的迁移,我建议从项目中排除任何JUnit 4的依赖性。这有助于识别由于编译步骤失败而留下的JUnit 4。它也减少了(意外地)为新测试重新引入JUnit 4的可能性。
原文: Testing Spring Boot Applications: Five Common Pitfalls - rieckpil