【问题标题】:Certain JUnit test take insane amounts of time in Ant某些 JUnit 测试在 Ant 中花费大量时间
【发布时间】:2020-07-24 17:02:15
【问题描述】:

相关:Ant Junit tests are running much slower via ant than via IDE - what to look at?

我和另一个问题有同样的问题,但我已经缩小了很多范围。

我有一套大型单元测试(即 800 个测试用例),应该由 Atlassian Bamboo CI 运行。我已经完全重写了我的构建脚本。

在 IntelliJ IDEA 的 test 文件夹中运行所有测试时,一切都需要 7 分钟的合理时间,包括基于 Spring 上下文的测试,需要填充有表的新 H2 实例。

在 Ant 中运行时,无论是在本地还是在 CI 上,构建都需要 疯狂 1 小时的时间来测试。让 8 个线程值班将时间减少到 20 分钟(最慢的工作)。

我曾怀疑最重的测试与 Spring 和内存数据库有关。我今天调查了很多,发现了一些性能很差的坏测试,但我在这里是因为我无法理解原因。

大多数表现糟糕的测试完全在内存中,几乎没有系统输出。虽然有相对“大量”的断言操作、迭代等,但相同的测试在 Ant 上的表现非常糟糕,在 IDE 上的表现也很好。

选择以下测试:

  • SortedLinkedListTests:1.163 秒

我有一个 SortedLinkedList 组件(带有 O(n) 排序插入的链表)及其单元测试。没有日志框架,没有 Spring 上下文。什么都没有。

基本上,单元测试运行 10000 次迭代。以下单个测试耗时 346 秒。

@Test
public void testAddWithoutComparator()
{
    final SortedLinkedList<Integer> uut = new SortedLinkedList<>();

    final Random random = new Random();

    for (int i = 0; i < ITERATIONS; i++)
    {
        uut.add(random.nextInt());

        Integer previous = null;
        if (i % 50 == 0)
            for (Integer candidate : uut)
            {
                assertNotNull(candidate);
                if (previous != null)
                    assertTrue(previous.compareTo(candidate) <= 0);
                previous = candidate;
            }
    }
}

也许代码有点笨,可以优化很多,但还不错,也没有那么糟糕。在 1 到 10000 次迭代中,它会检查列表是否在每一步都排序,而不仅仅是在开头或结尾。想想 10000 个增量测试。 这个测试的复杂度为 O(n^2),我对此很满意。我很高兴测试,没有向 IO 写入任何内容,在内存中运行了 4 秒。

同样,很多东西都可以简化或优化,但 4s 与 346s 对 IMO 来说太疯狂了

  • BeanUtilsTests

我有一个对属性和反射进行操作的 BeanUtils 实用程序。这个非常单一的测试,包括 AES 加密操作,需要 25 秒才能在 Ant 中运行

@Test
public void testMarshal_Password()
{
    String passwordPlaintext = RandomStringUtils.randomAlphanumeric(25);
    assertThat(BeanUtils.marshal(passwordPlaintext, PASSWORD), is(not(equalTo(passwordPlaintext))));
}

没有迭代!!!在内部,它是一个重定向到加密实用程序的 switch 语句。密钥在代码中是硬编码的,无需生成随机安全密钥。 Cipher.Init 然后加密随机字符串。

在 IDE 中需要 1s,初始化测试运行时可能比执行测试要多得多。

Ant 构建

<!-- JUNIT TARGETS -->
<target name="debug-junit">
    <property name="junit.include" value="**/*.java" />
    <condition property="jarOk">
        <resourcecount count="1">
            <fileset id="fs" dir="${project.artifactsDirectory}" includes="${artifact.name}-junit.jar"/>
        </resourcecount>
    </condition>
    <fail message="No jar file. Please package the project first" unless="jarOk"/>

    <mkdir dir="${junit.destDir}"/>

    <junit printsummary="yes" haltonfailure="yes" dir="${basedir}" forkmode="once" logfailedtests="true" maxmemory="1024m" fork="true" showoutput="false">
        <classpath refid="project.classpath.test"/>

        <jvmarg value="-Djava.compiler=NONE"/>
        <jvmarg value="-agentlib:jdwp=transport=dt_socket,address=${junit.debugPort},server=y,suspend=y"/>

        <batchtest todir="${junit.destDir}" haltonerror="false" haltonfailure="false" skipnontests="true">
            <fileset dir="${junit.srcDir}" includes="${junit.include}"/>
            <formatter type="xml" />
        </batchtest>
    </junit>
</target>

<target name="run-junit">
    <property name="junit.include" value="**/*.java" />
    <condition property="jarOk">
        <resourcecount count="1">
            <fileset id="fs" dir="${project.artifactsDirectory}" includes="${artifact.name}-junit.jar"/>
        </resourcecount>
    </condition>
    <fail message="No jar file. Please package the project first" unless="jarOk"/>

    <mkdir dir="${junit.destDir}"/>

    <junit printsummary="yes" haltonfailure="yes" dir="${basedir}" forkmode="once" logfailedtests="true" maxmemory="1024m" fork="true" showoutput="false">
        <classpath refid="project.classpath.test"/>

        <jvmarg value="-Djava.compiler=NONE"/>
        <jvmarg value="-ea"/>

        <batchtest todir="${junit.destDir}" haltonerror="false" haltonfailure="false" skipnontests="true">
            <fileset dir="${junit.srcDir}" includes="${junit.include}"/>
            <formatter type="xml" />
        </batchtest>
    </junit>
</target>

解释:

  • 当我想将调试器附加到调试测试时,junit.debugargs 被覆盖。在这里,您没有特定于调试的属性
  • fork="true" 只是为了确保类路径不受 Ant 相关类的感染
  • showoutput="false" 减少 IO。特别是,Bamboo 远程代理将stdout 传输给主服务器,从而降低了性能
  • forkmode 我也尝试过具有并行性的 perTest 模式。现在我恢复为单线程模式,因为 Atlassian Bamboo 在并行代理上运行
  • logfailedtests="true" 失败很重要,并非所有测试都失败

问题

有人帮忙吗?我需要我的构建在几乎没有体面的时间内运行

更新

更新 1:缩小范围

我现在正尝试适当地使用参数在本地 Ant 上运行单个测试类。我发现 SortedLinkedListTests 运行缓慢,所以我现在尝试单独运行它。这仍然需要很多时间。

我通过将另一个测试方法的迭代次数减少了一个数量级,设法减少了 BeanUtilsTests 的执行时间。然后,即使是密码测试也能像魅力一样运行。

更新 2:套件帮助

我试图回忆自从我从头开始重写构建并复制以来发生了什么变化。

我之前的构建有一个类似的run-junit Ant 任务,它只调用了一个@Suite-annotated 类文件,它引用了所有手动运行的测试。这是我重写构建的原因之一。我检查了 Bamboo 过去的测试日志。 SortedLinkedListTests 与之前的构建在几秒钟内执行

这是老跑者。我看不出有什么东西会降低性能如此艰难和糟糕

<target name="run-junit">
    <condition property="jarOk">
        <resourcecount count="1">
            <fileset id="fs" dir="${dir.publish}" includes="${artifact.name}-junit-*.jar"/>
        </resourcecount>
    </condition>
    <fail message="No jar file. Please package the project first" unless="jarOk"/>

    <mkdir dir="${dir.junitResult}"/>
    <property name="junit.srcDir" value="test"/>
    <fail message="Must set -Djunit.testSuite" unless:set="junit.testSuite"/>

    <junit printsummary="yes" haltonfailure="yes" dir="${basedir}" fork="true" showoutput="true">
        <classpath refid="project.classpath.test"/>
        <classpath location="build/compiled-test"/>

        <batchtest todir="${dir.junitResult}" haltonerror="false" haltonfailure="false" fork="true">
            <fileset dir="${junit.srcDir}" includes="**/${junit.testSuite}.java"/>
            <formatter type="xml"/>
        </batchtest>
        <formatter type="xml"/>
    </junit>
</target>

【问题讨论】:

    标签: java unit-testing junit ant


    【解决方案1】:

    经过数小时的尝试和失败后,以下内容对我有用

    删除

    <jvmarg value="-Djava.compiler=NONE"/>
    

    我将其标记为社区 wiki,以便任何人都能够通过解释进行扩展!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-10
      • 2021-10-25
      • 2015-03-14
      • 1970-01-01
      • 1970-01-01
      • 2022-12-02
      • 2023-03-11
      相关资源
      最近更新 更多