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

时间:2014-09-24 02:55:15

标签: java junit junit4 maven-surefire-plugin circleci

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

我想使用CircleCI并行,而不是Surefire fork and parallel execution选项。

1 个答案:

答案 0 :(得分:9)

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

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

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

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

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

    #!/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
    

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

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

    test:
      override:
        - ./bin/test.sh:
            parallel: true
    

    注意事项:

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

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

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

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

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

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