【问题标题】:How can I use Jenkins to run my integration tests in parallel?如何使用 Jenkins 并行运行集成测试?
【发布时间】:2013-06-24 01:03:36
【问题描述】:

现在我们有一个包含两个工作的项目。 1) 是带有单元测试的标准构建。 2)是集成测试。他们是这样工作的:

  1. 构建整个项目,运行单元测试,开始集成测试工作
  2. 构建整个项目,将其部署到集成服务器,针对集成服务器运行客户端集成测试

问题是第 2 步)现在需要一个多小时才能运行,我想并行化集成测试,以便它们花费更少的时间。但我不完全确定我可以/应该如何做到这一点。我的第一个想法是我可以有两个这样的步骤 2):

  1. 构建整个项目,运行单元测试,开始集成测试工作
  2. 构建整个项目,将其部署到集成服务器1,针对集成服务器1运行客户端集成测试
  3. 构建整个项目,将其部署到集成服务器2,针对集成服务器2运行客户端集成测试

但是,如何在集成服务器 1 上运行一半的集成测试,在集成服务器 2 上运行另一半?我正在使用 maven,所以我可能会用failsafe 和复杂的包含/排除模式找出一些东西。但这听起来像是需要付出很多努力才能维护的东西。 EG:当有人添加一个新的集成测试类时,我如何确保它在两台服务器之一上运行?开发者是否必须修改 maven 模式?

【问题讨论】:

    标签: maven jenkins continuous-integration build-automation


    【解决方案1】:

    是的,如果您有 2 个从属设备或一个具有 8 个执行程序的从属设备,并行测试执行器是一个很酷的插件,因为此插件基于“测试拆分”,例如:您将您的 junit 测试拆分为 4 个不同的数组,这些数组将在您指定的从属设备上的 4 个不同的执行程序上运行。我希望你明白了 :D,这取决于你想要运行并行测试的从站上的执行器数量,或者你应该将拆分测试数从 4 个减少到 2 个。

    【讨论】:

      【解决方案2】:

      我相信您现在已经找到了解决方案,但我会为打开此页面并提出相同问题的其他人留下一条路径:
      并行测试执行器插件:
      “这个插件添加了一个新的构建器,让您可以轻松地并行执行在单独的作业中定义的测试。这是通过让 Jenkins 查看上次运行的测试执行时间,将测试分成大小大致相等的多个单元,然后执行它们是并行的。”
      https://wiki.jenkins-ci.org/display/JENKINS/Parallel+Test+Executor+Plugin

      【讨论】:

      • 最好您也可以在答案中包含实际解决方案,以防将来此链接损坏。
      【解决方案3】:

      我发现this great article 了解如何执行此操作,但它提供了一种在 Groovy 代码中执行此操作的方法。我几乎遵循了这些步骤,但我还没有编写代码来按持续时间均匀分布测试。但这仍然是一个有用的工具,所以我会分享它。

      import junit.framework.JUnit4TestAdapter;
      import junit.framework.TestSuite;
      import org.junit.Ignore;
      import org.junit.extensions.cpsuite.ClassesFinder;
      import org.junit.extensions.cpsuite.ClasspathFinderFactory;
      import org.junit.extensions.cpsuite.SuiteType;
      import org.junit.runner.RunWith;
      import org.junit.runners.AllTests;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.Comparator;
      import java.util.List;
      
      @RunWith(AllTests.class)
      public class DistributedIntegrationTestRunner {
      
          private static Logger log = LoggerFactory.getLogger(DistributedIntegrationTestRunner.class);
      
          public static TestSuite suite() {
              TestSuite suite = new TestSuite();
      
              ClassesFinder classesFinder = new ClasspathFinderFactory().create(true,
                      new String[]{".*IntegrationTest.*"},
                      new SuiteType[]{SuiteType.TEST_CLASSES},
                      new Class[]{Object.class},
                      new Class[]{},
                      "java.class.path");
      
              int nodeNumber = systemPropertyInteger("node.number", "0");
              int totalNodes = systemPropertyInteger("total.nodes", "1");
      
              List<Class<?>> allTestsSorted = getAllTestsSorted(classesFinder);
              allTestsSorted = filterIgnoredTests(allTestsSorted);
              List<Class<?>> myTests = getMyTests(allTestsSorted, nodeNumber, totalNodes);
              log.info("There are " + allTestsSorted.size() + " tests to choose from and I'm going to run " + myTests.size() + " of them.");
              for (Class<?> myTest : myTests) {
                  log.info("I will run " + myTest.getName());
                  suite.addTest(new JUnit4TestAdapter(myTest));
              }
      
              return suite;
          }
      
          private static int systemPropertyInteger(String propertyKey, String defaultValue) {
              String slaveNumberString = System.getProperty(propertyKey, defaultValue);
              return Integer.parseInt(slaveNumberString);
          }
      
          private static List<Class<?>> filterIgnoredTests(List<Class<?>> allTestsSorted) {
              ArrayList<Class<?>> filteredTests = new ArrayList<Class<?>>();
              for (Class<?> aTest : allTestsSorted) {
                  if (aTest.getAnnotation(Ignore.class) == null) {
                      filteredTests.add(aTest);
                  }
              }
              return filteredTests;
          }
      
          /*
          TODO: make this algorithm less naive.  Sort each test by run duration as described here: http://blog.tradeshift.com/just-add-servers/
           */
          private static List<Class<?>> getAllTestsSorted(ClassesFinder classesFinder) {
              List<Class<?>> allTests = classesFinder.find();
              Collections.sort(allTests, new Comparator<Class<?>>() {
                  @Override
                  public int compare(Class<?> o1, Class<?> o2) {
                      return o1.getSimpleName().compareTo(o2.getSimpleName());
                  }
              });
              return allTests;
          }
      
          private static List<Class<?>> getMyTests(List<Class<?>> allTests, int nodeNumber, int totalNodes) {
              List<Class<?>> myTests = new ArrayList<Class<?>>();
      
              for (int i = 0; i < allTests.size(); i++) {
                  Class<?> thisTest = allTests.get(i);
                  if (i % totalNodes == nodeNumber) {
                      myTests.add(thisTest);
                  }
              }
      
              return myTests;
          }
      }
      

      ClasspathFinderFactory 用于查找与.*IntegrationTest 模式匹配的所有测试类。

      我做了 N 个作业,它们都运行 Runner,但它们都对 node.number 系统属性使用不同的值,因此每个作业运行一组不同的测试。这是故障保护插件的外观:

              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-failsafe-plugin</artifactId>
                  <version>2.12.4</version>
                  <executions>
                      <execution>
                          <id>integration-tests</id>
                          <goals>
                              <goal>integration-test</goal>
                              <goal>verify</goal>
                          </goals>
                      </execution>
                  </executions>
                  <configuration>
                      <includes>
                          <include>**/DistributedIntegrationTestRunner.java</include>
                      </includes>
                      <skipITs>${skipITs}</skipITs>
                  </configuration>
              </plugin>
      

      ClasspathFinderFactory 来自

              <dependency>
                  <groupId>cpsuite</groupId>
                  <artifactId>cpsuite</artifactId>
                  <version>1.2.5</version>
                  <scope>test</scope>
              </dependency>
      

      我认为应该有一些 Jenkins 插件来解决这个问题,但我一直找不到。接近的东西是Parallel Test Executor,但我不认为这和我需要的一样。看起来它在单个作业/服务器而不是多个服务器上运行所有测试。它没有提供一种明显的方式来表示“在这里运行这些测试,在那里运行这些测试”。

      【讨论】:

      • > 看起来它在单个作业/服务器而不是多个服务器上运行所有测试。当测试作业配置为“必要时执行并发构建”时,Jenkins 可能会在另一个节点上运行运行。
      猜你喜欢
      • 2014-07-03
      • 1970-01-01
      • 1970-01-01
      • 2011-02-02
      • 2019-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多