当前位置: 首页 > 知识库问答 >
问题:

编辑并重新运行Spring Boot单元测试,而无需重新加载上下文以加快测试速度

公羊学义
2023-03-14

我有一个SpringBoot应用程序,并使用postgres测试容器编写了单元测试(https://www.testcontainers.org/)还有朱尼特。测试具有@SpringBootTest注释,该注释在运行测试之前加载上下文并启动测试容器。

在我比较旧的Macbook上加载上下文和启动容器大约需要15秒,但是测试本身非常快(

我知道IntelliJ和Springboot支持在应用程序运行时热重新加载类。对于单元测试,是否有类似的解决方案/建议?i、 e保持上下文加载和testcontainer(DB)运行,但只重新编译修改后的测试类并再次运行选定的测试。

共有3个答案

宰父才
2023-03-14

我无法想象测试中会出现一些热重新加载的神奇标志——有太多的东西会弄脏spring上下文,弄脏数据库等等。

在我看来,这里最容易做的事情是用manual container start本地替换测试容器初始值设定项,并更改数据库的属性以指向该容器。如果您希望实现自动化,您可以添加启动前脚本(如果您使用IntelliJ…)来执行类似操作:docker start postgres | | docker run postgres(linux),如果容器未运行,它将启动容器,如果容器正在运行,则不执行任何操作。

通常IDE重新编译只是改变受影响的类无论如何,Spring上下文可能不会在没有容器启动的情况下启动15秒,除非你也有很多bean要配置...

汪丁雷
2023-03-14

我不认为你能保持上下文加载。

您可以做的是激活test容器中的可重用容器功能。它防止容器在测试运行后被破坏。

您必须确保您的测试是幂等的,或者它们在完成后删除了对容器所做的所有更改。

简而言之,您应该将.withReuse(true)添加到容器定义中,并将testcontainers.reuse.enable=true添加到~/.testcontainers.properties中(这是homedir中的一个html" target="_blank">文件)

下面是我如何定义testcontainer来使用Oracle测试代码。

import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.OracleContainer;

public class StaticOracleContainer {
    public static OracleContainer getContainer() {
        return LazyOracleContainer.ORACLE_CONTAINER;
    }

    private static class LazyOracleContainer {
        private static final OracleContainer ORACLE_CONTAINER = makeContainer();

        private static OracleContainer makeContainer() {
            final OracleContainer container = new OracleContainer()
                    // Username which testcontainers is going to use
                    // to find out if container is up and running
                    .withUsername("SYSTEM")
                    // Password which testcontainers is going to use
                    // to find out if container is up and running
                    .withPassword("123")
                    // Tell testcontainers, that those ports should
                    // be mapped to external ports
                    .withExposedPorts(1521, 5500)
                    // Oracle database is not going to start if less
                    // than 1gb of shared memory is available, so this is necessary
                    .withSharedMemorySize(2147483648L)
                    // This the same as giving the container
                    // -v /path/to/init_db.sql:/u01/app/oracle/scripts/startup/init_db.sql
                    // Oracle will execute init_db.sql, after container is started
                    .withClasspathResourceMapping("init_db.sql"
                            , "/u01/app/oracle/scripts/startup/init_db.sql"
                            , BindMode.READ_ONLY)
                     // Do not destroy container
                     .withReuse(true)
;

            container.start();
            return container;
        }
    }
}

正如你所看到的,这是一个单身汉。我需要它来手动控制testcontainers的生命周期,这样我就可以使用可重用的容器。如果您想知道如何使用此单例将Oracle添加到Spring测试上下文中,您可以看看我使用testcontainers的示例。https://github.com/poxu/testcontainers-spring-demo

不过,这种方法有一个问题。Testcontainers永远不会停止可重用容器。您必须手动停止并销毁容器。

白光耀
2023-03-14

我相信你的问题有一个简单的解决方案。您还没有具体说明如何在测试中运行测试容器,但是我有以下方法的成功经验:

对于在本地运行的测试,在笔记本电脑上启动一次postgres服务器(比如在工作日开始时)。它可以是停靠进程,甚至是常规postgresql安装。

在测试期间,Spring Boot应用程序并不真正知道它与测试容器交互——它获取主机/端口/凭据,就这样——它使用这些参数创建了一个数据源。

因此,对于您的本地开发,您可以修改与测试容器的集成,以便只有在没有“local.test.MODE”env的情况下才会启动实际的测试容器。变量定义(基本上,您可以选择任何名称-它不存在)。

然后,在您的笔记本电脑上定义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的启动时间有点超出了这个问题的范围,尽管肯定有这样做的方法,例如:查看这个线程和更新的spring版本,如果您在这里使用它。所以我假设你会想出这样或那样的方法,为什么它开始缓慢

无论如何,您可以如何处理此类信息,以及如何使应用程序上下文加载过程更快?很明显,您可以从配置中排除慢速bean/一组bean,也许您在测试中根本不需要它,或者至少可以使用@MockBean——这取决于实际的用例。在某些情况下,还可以提供配置,这些配置仍然会加载那个慢bean,但会改变它的行为,使它不会变慢。

我还可以指出“普遍适用的想法”,无论您的实际代码基础如何,这些想法都会有所帮助。

首先,如果您正在运行共享完全相同配置的不同测试用例(IDE中的多选测试并同时运行它们),那么spring boot足够智能,不会重新初始化应用程序上下文。这称为“缓存中的应用程序上下文缓存”。这里是关于这个主题的众多教程之一。

另一种方法是使用惰性bean初始化。在Spring2.2中有一个属性

spring:
  main:
    lazy-initialization: true

当然,如果您不打算在生产中使用它,请在您选择的src/test/resource的配置文件中定义它。spring boot在测试期间也会读取它,只要它遵守命名约定。如果您对此有技术问题。(再次超出问题的范围),然后考虑阅读本教程。

如果您的Spring靴的年龄大于2.2,您可以尝试“手动”操作:以下是方法

我想提到的最后一个方向是——重新考虑您的测试实现。如果您有一个大项目要测试,这一点尤其重要。通常,应用程序是分层的,如服务、DAO-s、控制器等。我的观点是,涉及DB的测试应该只用于DAO的层——这是测试SQL查询的地方。业务逻辑代码通常不需要DB连接,通常可以在根本不使用spring的单元测试中涵盖。因此,与使用启动整个应用程序上下文的@springbootest注释不同,您只能运行DAO的配置,这样启动速度更快,“慢bean”属于应用程序的其他部分。Spring boot甚至有一个特殊的注释(它们对所有内容都有注释;)@DataJpaTest

这是基于这样一种想法,即整个spring测试包仅用于集成测试,一般来说,启动spring的测试是集成测试,并且您可能更喜欢在可能的情况下使用单元测试,因为单元测试速度更快,并且不使用外部依赖项:数据库、远程服务等。

第二个问题:模式经常不同步

在我当前的方法中,测试容器启动,liquibase应用我的模式,然后执行测试。一切都是在IDE中完成的,这更方便一些。

我承认我没有使用过liquibase,我们使用了flyway,但我相信答案是一样的。

简言之,这将继续这样工作,你不需要改变任何事情。

我会解释的。

Liquibase应该与spring应用程序上下文一起启动,它应该应用迁移,这是事实。但是在实际应用迁移之前,它应该检查迁移是否已经应用,如果数据库是同步的,它将什么也不做。Flyway在数据库中维护了一个表,我相信liquibase也使用了类似的机制

因此,只要你没有创建表或测试的东西,你应该可以去:

假设您是第一次启动Postgres服务器,您“在工作日开始时”运行的第一个测试将在上述用例的帮助下创建一个模式并部署所有表、索引等,然后将启动测试。

但是,现在当您开始第二个测试时,迁移已经应用。这相当于在非测试场景(登台、生产等)中重新启动应用程序本身——重新启动本身不会真正将所有迁移应用到DB。这里也一样。。。

好的,这是一个简单的例子,但是您可能会在测试中填充数据(好吧,您应该是;),这就是为什么我提到有必要在原始答案中对测试本身添加@Transactional注释。

此注释在运行测试中的所有代码之前创建一个事务,并人工将其回滚读取,删除测试中填充的所有数据,尽管测试已通过

现在让它变得更复杂,如果您在测试中创建表、更改现有表上的列呢?嗯,仅仅这一点就会让你的liquibase变得疯狂,即使是在生产场景中,所以你可能不应该这样做,但是再次将@Transactional放在测试本身上会有所帮助,因为PostgreSQL的DDL(只是为了澄清DDL=数据定义语言,所以我指的是命令与ALTER TABLE一样,基本上任何改变现有模式的命令都是事务性的。例如,我知道Oracle没有在事务中运行DDL命令,但是从那以后事情可能已经改变了。

 类似资料:
  • 问题内容: 我有一个包含2个测试的测试类: 当我单独运行测试时,我不会出错,但是当我同时运行所有测试时,会失败。失败是由于某些测试修改了应用程序上下文导致的: 是否可以单独运行此测试?我只想在启动test1时读取所有必需的东西,然后运行测试,然后关闭所有必需的东西。然后启动test2。 问题答案: 您可以在修改应用程序上下文的测试类上使用@DirtiesContext批注。 Java文档 Spri

  • 问题内容: 我有一个分类广告网站,在显示广告的页面上,我正在创建“向朋友发送提示”表单… 因此,任何想要的人都可以将广告提示发送给某些朋友的电子邮件地址。 我猜该表格必须提交到php页面,对吗? 提交表单时,页面被重新加载…我不想要… 有什么办法可以使其不重新加载并仍然发送邮件?最好没有ajax或jquery … 谢谢 问题答案: 您需要提交ajax请求来发送电子邮件,而无需重新加载页面。 您的代

  • 问题内容: 我需要在测试类的单个方法中更改applicationContext中活动的Spring概要文件,并且由于我使用的是ProfileResolver,因此在刷新竞赛之前,我需要运行一行代码。我尝试了以下方法: 但是我得到: DirtiesContext对我不起作用,因为它是在类/方法执行之后而不是之前运行的,并且无论如何我都需要在运行刷新/重新加载之前执行一行代码。 有什么建议?我试图查看

  • 问题内容: 目标:在Eclipse中找到一种方法来执行JUnit类中的所有测试,该方法在每次测试之前而不是一次重新加载Spring上下文。 场景:我继承了DAO测试套件,该套件使用了HSQL内存数据库,该数据库在上下文加载时使用一些示例数据进行了初始化。在运行测试时,我注意到如果执行了整个类,则所有测试都将通过。但是特定的测试方法在单独执行时会失败。显然,测试不是独立的,早期测试会更改数据库状态并

  • 目标:在Eclipse中找到一种方法来执行JUnit类中的所有测试,该类在每次测试之前重新加载Spring上下文,而不是一次。 场景:我继承了DAO测试套件,它使用内存中的HSQL数据库,该数据库在上下文加载时使用一些示例数据进行初始化。在运行测试时,我注意到如果执行整个类,所有测试都会通过。但具体的测试方法在单独执行时失败。显然,这些测试并不是独立的,早期的测试正在改变数据库状态,而不是在它们自

  • 问题内容: 我将如何更新每两秒钟在服务器上更新一次的映像,而无需用户按下刷新按钮,我的第一个猜测是ajax,但我之前从未真正使用过它。有人能指出我正确的方向吗? 编辑:忘记提及图像是由perl脚本生成的.gif-尝试通过url抓取它返回脚本 问题答案: 您的Perl脚本似乎有问题。尝试通过URL访问图像应该仍然返回图像。它应该返回二进制数据而不是脚本。您还应该将响应的Content- type标头