如何在Java/Maven/Surefire项目中利用CircleCI并行性?

JBC*_*BCP 5 java junit junit4 maven-surefire-plugin circleci

我有一个使用Maven和maven-surefire-plugin运行JUnit 4测试的Java项目.我正在使用CircleCI构建.如何启用并行性以便我的测试套件运行得更快?

我想使用CircleCI并行,而不是Surefire fork和并行执行选项.

JBC*_*BCP 9

maven-surefire-plugin支持它不支持并行性,至少不是以隔离方式支持CircleCI(每个测试执行的单独节点).

但是,您可以使用两种方法手动启用CircleCI样式的并行性:

  1. 使用shell脚本选择要按节点运行的测试,然后使用该-Dtest参数.
  2. 自定义JUnit 4 TestRule

Shell脚本

bin如果您还没有目录,请在项目中创建一个目录.

bin,在您的项目中创建一个shell脚本test.sh,其中包含以下内容

#!/bin/bash

NODE_TOTAL=${CIRCLE_NODE_TOTAL:-1}
NODE_INDEX=${CIRCLE_NODE_INDEX:-0}

i=0
tests=()
for file in $(find ./src/test/java -name "*Test.java" | sort)
do
  if [ $(($i % ${NODE_TOTAL})) -eq ${NODE_INDEX} ]
  then
    test=`basename $file | sed -e "s/.java//"`
    tests+="${test},"
  fi
  ((i++))
done

mvn -Dtest=${tests} test
Run Code Online (Sandbox Code Playgroud)

此脚本将在您的src/test/java目录中搜索所有以文件结尾的文件Test.java,并将其-Dtest作为逗号分隔列表添加到参数中,然后调用maven.

要启用新的测试脚本,请将以下内容放入您的circle.yml文件中:

test:
  override:
    - ./bin/test.sh:
        parallel: true
Run Code Online (Sandbox Code Playgroud)

注意事项:

  1. 如果文件名不遵循此命名约定,文件位于其他位置,或者您需要运行不同的生命周期阶段,则可能需要自定义此脚本.
  2. 如果您有大量测试,则可能会发现您的-Dtest参数超出了Linux命令行的最大长度.

Junit4 TestRule

您可以使用自定义TestRule在Java代码中执行与上述类似的操作.这样可以减少CircleCI定制配置的优势,但在Java框架上对CircleCI进行了一些假设.

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

@Slf4j
final class CircleCiParallelRule implements TestRule {
    @Override
    public Statement apply(Statement statement, Description description) {

        boolean runTest = true;

        final String tName = description.getClassName() + "#" + description.getMethodName();

        final String numNodes = System.getenv("CIRCLE_NODE_TOTAL");
        final String curNode = System.getenv("CIRCLE_NODE_INDEX");

        if (StringUtils.isBlank(numNodes) || StringUtils.isBlank(curNode)) {
            log.trace("Running locally, so skipping");
        } else {
            final int hashCode = Math.abs(tName.hashCode());

            int nodeToRunOn = hashCode % Integer.parseInt(numNodes);
            final int curNodeInt = Integer.parseInt(curNode);

            runTest = nodeToRunOn == curNodeInt;

            log.trace("currentNode: " + curNodeInt + ", targetNode: " + nodeToRunOn + ", runTest: " + runTest);

            if (!runTest) {
                return new Statement() {
                    @Override
                    public void evaluate() throws Throwable {
                        Assume.assumeTrue("Skipping test, currentNode: " + curNode + ", targetNode: " + nodeToRunOn, false);
                    }
                };
            }
        }

        return statement;
    }
}
Run Code Online (Sandbox Code Playgroud)

(注意我在上面的代码中使用Project Lombok(日志实例化)和Apache Commons-Lang(用于StringUtils),但如果需要,可以很容易地消除这些.

要启用此功能,请在测试基类中执行此操作以逐个测试地进行平衡:

// This will load-balance across multiple CircleCI nodes
@Rule public CircleCiParallelRule className = new CircleCiParallelRule();
Run Code Online (Sandbox Code Playgroud)

或者,如果您想逐个级地平衡,您可以这样做:

// This will load-balance across multiple CircleCI nodes
@ClassRule public CircleCiParallelRule className = new CircleCiParallelRule();
Run Code Online (Sandbox Code Playgroud)