某些JUnit测试在Ant中花费了极长的时间。

huangapple go评论76阅读模式
英文:

Certain JUnit test take insane amounts of time in Ant

问题

相关链接:https://stackoverflow.com/questions/123127/ant-junit-tests-are-running-much-slower-via-ant-than-via-ide-what-to-look-at

我遇到了与另一个问题相同的问题,但我已经大大缩小了范围。

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

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

但是,在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秒内运行。

同样,很多东西可以简化或优化,但是4秒与346秒相比,我觉得是疯狂的。

  • BeanUtilsTests

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

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

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

在IDE中只需1秒,可能初始化测试运行时所花费的时间比执行测试本身还要多。

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="
英文:

Related: https://stackoverflow.com/questions/123127/ant-junit-tests-are-running-much-slower-via-ant-than-via-ide-what-to-look-at

I am having the same problem as the other question, but I have narrowed the scope a lot.

I have a large suite of unit test (namely 800 test cases) which should be run by Atlassian Bamboo CI. I have rewritten my build script entirely.

When running all tests in my test folder in IntelliJ IDEA, everything takes a 7-minutes reasonable time, including Spring-context-based tests requiring a new instance of H2 populated with tables.

When running in Ant, both locally and on CI, the build takes an insane 1-hour time to test. Putting 8 threads on duty reduces the time to 20 minutes (the slowest job).

I had suspected that the heaviest tests were related to Spring and in memory database. I have investigated a lot today and found a shortlits of bad tests that perform awfully, but I am here because I can't understand the reason.

Most of awfully-performing tests are entirely in-memory with little to none system output. While there is a relatively "large" number of assertion operations, iterations, etc. the same test performs awfully on Ant and performs great on IDE.

Pick the following tests:

  • SortedLinkedListTests: 1.163 seconds

I have a SortedLinkedList component (linked list with O(n) sorted insertion) along with its unit tests. No logging framework, no Spring context. Nothing.

Basically the unit tests operate on 10000 iterations. The following very single test took 346 seconds.

@Test
public void testAddWithoutComparator()
{
    final SortedLinkedList&lt;Integer&gt; uut = new SortedLinkedList&lt;&gt;();

    final Random random = new Random();

    for (int i = 0; i &lt; 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) &lt;= 0);
                previous = candidate;
            }
    }
}

Maybe the code is a bit dumb, can be optimized a lot, but it's not so bad, not that bad. On 1 to 10000 iterations, it checks that the list is sorted at every step, not just at the beginning or at the end. Think about 10000 incremental tests.
This test has a complexity O(n^2), and I am happy with that. I am happy that the test, not writing anything to IO, runs in 4 seconds in memory.

Again, a lot can be simplified, or optimized, but 4s vs 346s is insane IMO

  • BeanUtilsTests

I have a BeanUtils utility that operates on properties and reflections. This very single test, including an AES encryption operation, takes 25 seconds to run in Ant

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

No iterations!!! Internally, it is a switch statement that redirects to an encryption utility. The key is hardcoded in the code, no need to generate random secure keys. Cipher.Init and then encrypt the random string.

It takes 1s in IDE, probably a lot more time to initialize testing runtime than perform the test.

The Ant build

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

    &lt;mkdir dir=&quot;${junit.destDir}&quot;/&gt;

    &lt;junit printsummary=&quot;yes&quot; haltonfailure=&quot;yes&quot; dir=&quot;${basedir}&quot; forkmode=&quot;once&quot; logfailedtests=&quot;true&quot; maxmemory=&quot;1024m&quot; fork=&quot;true&quot; showoutput=&quot;false&quot;&gt;
        &lt;classpath refid=&quot;project.classpath.test&quot;/&gt;

        &lt;jvmarg value=&quot;-Djava.compiler=NONE&quot;/&gt;
        &lt;jvmarg value=&quot;-agentlib:jdwp=transport=dt_socket,address=${junit.debugPort},server=y,suspend=y&quot;/&gt;

        &lt;batchtest todir=&quot;${junit.destDir}&quot; haltonerror=&quot;false&quot; haltonfailure=&quot;false&quot; skipnontests=&quot;true&quot;&gt;
            &lt;fileset dir=&quot;${junit.srcDir}&quot; includes=&quot;${junit.include}&quot;/&gt;
            &lt;formatter type=&quot;xml&quot; /&gt;
        &lt;/batchtest&gt;
    &lt;/junit&gt;
&lt;/target&gt;

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

    &lt;mkdir dir=&quot;${junit.destDir}&quot;/&gt;

    &lt;junit printsummary=&quot;yes&quot; haltonfailure=&quot;yes&quot; dir=&quot;${basedir}&quot; forkmode=&quot;once&quot; logfailedtests=&quot;true&quot; maxmemory=&quot;1024m&quot; fork=&quot;true&quot; showoutput=&quot;false&quot;&gt;
        &lt;classpath refid=&quot;project.classpath.test&quot;/&gt;

        &lt;jvmarg value=&quot;-Djava.compiler=NONE&quot;/&gt;
        &lt;jvmarg value=&quot;-ea&quot;/&gt;

        &lt;batchtest todir=&quot;${junit.destDir}&quot; haltonerror=&quot;false&quot; haltonfailure=&quot;false&quot; skipnontests=&quot;true&quot;&gt;
            &lt;fileset dir=&quot;${junit.srcDir}&quot; includes=&quot;${junit.include}&quot;/&gt;
            &lt;formatter type=&quot;xml&quot; /&gt;
        &lt;/batchtest&gt;
    &lt;/junit&gt;
&lt;/target&gt;

Explanations:

  • junit.debugargs is overwritten when I want to attach a debugger to debug tests. Here you don't have debug-specific properties
  • fork=&quot;true&quot; just to make sure that classpath is not <strike>infected</strike> affected by Ant-related classes
  • showoutput=&quot;false&quot; to reduce IO. In particular, Bamboo remote agents transmit stdout to master, reducing performance
  • forkmode I also have tried perTest mode with parallelism. Now I reverted to single-threading mode because Atlassian Bamboo runs on parallel agents
  • logfailedtests=&quot;true&quot; failures are important, not all tests fail

Question

Somebody help please? I need my build to run in a barely decent time

Updates

> Update 1: narrowing the scope

I am now trying to use arguments appropriately to run a single test class on my local Ant. I found SortedLinkedListTests to be running slow, so I am now trying to run it alone. It still takes a lot of time.

I have managed to reduce the execution time of BeanUtilsTests by reducting the number of iterations of another test method by a magnitude. And then, even the password test runs like a charm.

> Update 2: suite helps

I was trying to recall what has changed since I rewrote my build from scratch, and replicate.

My previous build had a similar run-junit Ant task that only invoked a single @Suite-annotated class file, which referenced all tests to run manually. This is one of the reasons I rewrote the build. I have checked Bamboo for past logs of tests. SortedLinkedListTests executed in a few seconds with the previous build

This is the old runner. I can't see anything that would drop the performance so hard and bad

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

    &lt;mkdir dir=&quot;${dir.junitResult}&quot;/&gt;
    &lt;property name=&quot;junit.srcDir&quot; value=&quot;test&quot;/&gt;
    &lt;fail message=&quot;Must set -Djunit.testSuite&quot; unless:set=&quot;junit.testSuite&quot;/&gt;

    &lt;junit printsummary=&quot;yes&quot; haltonfailure=&quot;yes&quot; dir=&quot;${basedir}&quot; fork=&quot;true&quot; showoutput=&quot;true&quot;&gt;
        &lt;classpath refid=&quot;project.classpath.test&quot;/&gt;
        &lt;classpath location=&quot;build/compiled-test&quot;/&gt;

        &lt;batchtest todir=&quot;${dir.junitResult}&quot; haltonerror=&quot;false&quot; haltonfailure=&quot;false&quot; fork=&quot;true&quot;&gt;
            &lt;fileset dir=&quot;${junit.srcDir}&quot; includes=&quot;**/${junit.testSuite}.java&quot;/&gt;
            &lt;formatter type=&quot;xml&quot;/&gt;
        &lt;/batchtest&gt;
        &lt;formatter type=&quot;xml&quot;/&gt;
    &lt;/junit&gt;
&lt;/target&gt;

答案1

得分: 1

以下是您要的翻译内容:

经过数小时的尝试和失败,以下方法对我起作用了:

移除:

&lt;jvmarg value=&quot;-Djava.compiler=NONE&quot;/&gt;

我将这标记为社区共享页面,以便任何人都可以根据解释进行扩展!

英文:

The following worked for me after hours of try-and-fail

Remove

&lt;jvmarg value=&quot;-Djava.compiler=NONE&quot;/&gt;

I am marking this as a community wiki in order for anyone to be able to expand with an explanation!

huangapple
  • 本文由 发表于 2020年7月25日 01:02:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/63078218.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定