我如何使用Jenkins并行运行我的集成测试?

Dan*_*lan 5 build-automation continuous-integration maven jenkins

现在我们有一个项目,可以建立两个工作岗位.1)标准构建是否具有单元测试.2)是集成测试.他们的工作方式如下:

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

问题是步骤2)现在运行需要一个多小时,我想并行化集成测试,以便他们花更少的时间.但我不确定我该怎么做/应该这样做.我的第一个想法是,我可以有两个步骤2)这样:

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

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

Dan*_*lan 2

我发现这篇关于如何执行此操作的精彩文章,但它提供了一种在 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用于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>
Run Code Online (Sandbox Code Playgroud)

ClasspathFinderFactory来自于

        <dependency>
            <groupId>cpsuite</groupId>
            <artifactId>cpsuite</artifactId>
            <version>1.2.5</version>
            <scope>test</scope>
        </dependency>
Run Code Online (Sandbox Code Playgroud)

我认为应该有一些 Jenkins 插件可以实现这一点,但我还没找到。接近的是Parallel Test Executor,但我不认为这能做我需要的同样的事情。看起来它在单个作业/服务器而不是多个服务器上运行所有测试。它没有提供一种明显的方式来表达“在这里运行这些测试,在那里运行那些测试”。