我相信有一个简单的解决方案可以解决您的问题。您还没有指定在测试中究竟如何运行测试容器,但是我对以下方法有成功的经验:
对于在本地运行的测试 - 在您的笔记本电脑上启动一次 postgres 服务器(比如在工作日开始时或其他时间)。它可以是 dockerized 进程,甚至可以是常规的 postgresql 安装。
在测试期间,spring boot 应用程序并不真正知道它与测试容器交互 - 它获取主机/端口/凭据,仅此而已 - 它根据这些参数创建一个 DataSource。
因此,对于您的本地开发,您可以修改与测试容器的集成,以便只有在没有“LOCAL.TEST.MODE”环境时才会启动实际的测试容器。定义的变量(基本上你可以选择任何名称 - 它不存在)。
然后,在您的笔记本电脑上定义 ENV 变量(或者您可以为此使用系统属性 - 任何对您更好的方法),然后配置 spring boot 的数据源以获取本地安装的属性(如果定义了该系统属性):
简而言之,它可能是这样的:
@Configuration
@ConditionalOnProperty(name = "test.local.mode", havingValue = "true", matchIfMissing = false)
public class MyDbConfig {
@Bean
public DataSource dataSource () {
// create a data source initialized with local credentials
}
}
当然,可以实现更“聪明”的配置属性解决方案,这完全取决于您如何与测试容器集成以及数据源初始化的实际属性来自哪里,但想法将保持不变:
在您的本地环境中。您实际上将使用本地安装的 PostgreSQL 服务器,甚至不会启动测试容器
由于 postgresql 中包括 DDL 在内的所有操作都是事务性的,因此您可以在测试中添加 @Transactional 注释,spring 将回滚测试所做的所有更改,以免数据库充满垃圾数据。
相对于测试容器,这种方法有一个显着的优势:
如果您的测试失败并且一些数据保留在数据库中,您可以在本地进行检查,因为服务器将保持活动状态。因此,您将能够使用 PG Admin 或其他工具连接到数据库并检查状态...
更新 1
基于 op 的评论
我明白你的意思,基本上,你提到了两个不同的问题,我将尝试分别提及
问题 1 应用程序上下文大约需要 10-12 秒才能启动。
好的,这需要调查。可能有一些 bean 被缓慢初始化。所以你应该明白为什么应用程序启动如此缓慢:
Spring 的代码(扫描、bean 定义填充等)适用于一秒钟的粒子,通常本身不是瓶颈 - 它必须在某处您的应用程序。
检查 bean 启动时间有点超出了这个问题的范围,尽管肯定有方法可以这样做,例如:
see this thread 和较新的弹簧版本,如果您使用执行器 this here。所以我假设你会弄清楚为什么它开始缓慢
不管怎样,你可以用这些信息做什么,以及如何让应用程序上下文加载过程更快?
好吧,显然您可以从配置中排除慢速 bean/bean 集,也许您在测试中根本不需要它,或者至少可以使用 @MockBean 代替 - 这取决于实际用例。
在某些情况下,它还可以提供配置,这些配置仍然会加载该慢速 bean,但会改变其行为以使其不会变慢。
我还可以指出“普遍适用的想法”,无论您的实际代码库如何,都可以提供帮助。
首先,如果您正在运行共享完全相同配置的不同测试用例(IDE 中的多选测试并同时运行它们),那么 Spring Boot 足够智能,不会重新初始化应用程序语境。这称为“在缓存中缓存应用程序上下文”。 Here is one of the numerous tutorials关于这个话题。
另一种方法是使用惰性 bean 初始化。在 spring 2.2+ 中有一个属性
spring:
main:
lazy-initialization: true
当然,如果您不打算在生产中使用它,请在您选择的src/test/resource 的配置文件中定义它。只要符合命名约定,spring-boot 也会在测试期间读取它。如果您对此有技术问题。 (再次超出问题的范围),然后考虑阅读this tutorial
如果您的 spring boot 早于 2.2,您可以尝试“手动”执行此操作:here is how
我想提到的最后一个方向是 - 重新考虑您的测试实施。如果您有一个要测试的大项目,这一点尤其重要。通常,应用程序有分层,如服务、DAO-s、控制器,你知道的。我的观点是,涉及 DB 的测试应该只用于 DAO 层——这是您测试 SQL 查询的地方。
业务逻辑代码通常不需要数据库连接,通常可以在完全不使用 spring 的单元测试中覆盖。因此,您可以只运行 DAO 的配置,而不是使用启动整个应用程序上下文的 @SpringBootTest 注释,这可能会启动得更快,并且“慢 bean”属于应用程序的其他部分。 Spring boot 甚至有一个特殊的注解(它们对所有东西都有注解;))@DataJpaTest.
这是基于整个 spring 测试包仅用于集成测试的想法,通常,您开始 spring 的测试是集成测试,您可能更喜欢尽可能使用单元测试,因为它们速度更快,并且不使用外部依赖项:数据库、远程服务等。
第二个问题:架构经常不同步
在我目前的方法中,测试容器启动,liquibase 应用我的架构,然后执行测试。一切都在 IDE 中完成,更方便一些。
我承认我没有使用过 liquibase,我们使用的是 flyway,但我相信答案会是一样的。
简而言之 - 这将继续这样工作,您无需更改任何内容。
我会解释的。
Liquibase 应该与 spring 应用程序上下文一起启动,它应该应用迁移,这是真的。但在实际应用迁移之前,它应该检查迁移是否已经应用,如果数据库是同步的,它什么也不做。为此,Flyway 在数据库中维护了一个表,我确信 liquibase 使用了类似的机制。
因此,只要您不创建表格或进行测试,您就可以开始了:
假设您是第一次启动 Postgres 服务器,您“在工作日开始时”运行的第一个测试,按照上述用例将创建一个模式并部署所有表、索引、等在 liquibase 迁移的帮助下,然后将开始测试。
但是,现在当您开始第二次测试时 - 迁移将已应用。这相当于在非测试场景(暂存、生产等)中重新启动应用程序本身 - 重新启动本身不会真正将所有迁移应用到数据库。这里也是一样...
好的,这是简单的情况,但您可能会在测试中填充数据(嗯,您应该是;))这就是为什么我提到有必要在原始测试中添加 @Transactional 注释回答。
此注释在运行测试中的所有代码之前创建一个事务并人为回滚它 - 读取,删除测试中填充的所有数据,尽管测试已通过
现在让事情变得更复杂,如果您在测试中创建表、更改现有表上的列怎么办?好吧,仅此一项就会使您的 liquibase 即使在生产场景中也很疯狂,因此您可能不应该这样做,但是再次将 @Transactional 放在测试本身上会有所帮助,因为 PostgreSQL 的 DDL(只是为了澄清 DDL = 数据定义语言,所以我的意思是像ALTER TABLE 这样的命令,基本上是任何改变现有模式的命令)命令也是事务性的。我知道例如 Oracle 并没有在事务中运行 DDL 命令,但从那时起事情可能已经发生了变化。