2013-06-26 6 views
5

Прямо сейчас у нас есть проект, который строится на двух работах. 1) Стандартная сборка с модульными испытаниями. 2) является интеграционным тестом. Они работают так:Как я могу использовать Jenkins для параллельного запуска моих интеграционных тестов?

  1. построить весь проект, запустить юнит-тесты, начать тест интеграции работы
  2. построить весь проект, развернуть его на сервер интеграции, запускать клиентские тесты на сторону интеграции против интеграции сервера

Проблема заключается в шаге 2) теперь требуется более часа для запуска, и я хотел бы распараллеливать интеграционные тесты, чтобы они занимали меньше времени. Но я не совсем уверен, как я могу/должен это делать. Моя первая мысль, что я мог бы иметь два шага 2) s, как это:

  1. построить весь проект, запустить юнит-тесты, начать интеграционный тест работа
  2. построить весь проект, развернуть его на интеграции server1, запускать клиентские тесты на стороне интеграции против интеграции server1
  3. построить весь проект, развернуть его на интеграции server2, запускать клиентские тесты на стороне интеграции против интеграции server2

Но тогда, как мне выполнить половину интеграционных тестов на сервере интеграции1, а другая половина - на сервере интеграции2? Я использую maven, поэтому я мог бы, возможно, выяснить что-то с failsafe, а комплекс включает/исключает шаблон. Но это звучит как нечто, что потребует больших усилий для поддержания. EG: когда кто-то добавляет новый тестовый класс интеграции, как я могу убедиться, что он запускается на одном из двух серверов? Требуется ли разработчику модифицировать шаблоны maven?

ответ

2

Я нашел 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 The используется, чтобы найти все тестовые классы, которые соответствуют .*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> 

Я думаю, что должен быть какой-то Дженкинс плагин для этого, но я не смог найти. То, что близко, это Parallel Test Executor, но я не думаю, что это делает то же самое, что мне нужно. Похоже, что он запускает все тесты на одном задании/сервере вместо нескольких серверов. Это не дает очевидного способа сказать: «Запустите эти тесты здесь и те тесты там».

+0

> Похоже, что все тесты проводятся на одно задание/сервер вместо нескольких серверов. Когда тестовое задание настроено на «Выполнять параллельные сборки, если необходимо», прогоны могут выполняться на другом узле Дженкинсом. –

1

Я полагаю, вы уже нашли решение сейчас, но я оставлю путь для тех, кто будет открывать эту страницу задает тот же вопрос:
Параллельный тест исполнитель плагин:
«Этот плагин добавляет новый который позволяет вам легко выполнять тесты, определенные в отдельном задании параллельно. Это достигается за счет того, что Дженкинс смотрит на время выполнения теста последнего запуска, разбивает тесты на несколько единиц примерно равного размера, а затем выполняет их параллельно ».
https://wiki.jenkins-ci.org/display/JENKINS/Parallel+Test+Executor+Plugin

+0

Было бы лучше, если бы вы могли также включить фактическое решение в свой ответ в случае, если эта ссылка будет нарушена в будущем. – Christina

0

Да, Parallel Test Executor прохладный плагин, если у вас есть 2 раба или одного раба с 8 исполнителем, потому что этот плагин основан на «тестах расщепления», так, например: вы разбили тесты JUnit в 4 разные array, эти массивы будут выполняться на 4 разных исполнителях на этом подчиненном устройстве, что вы указали. Надеюсь, вы получили это: D, это зависит от количества исполнителей на этом ведомом, где вы хотите провести параллельное тестирование, или вы должны уменьшить количество тестов сплит-тестов до 2 из 4.

 Смежные вопросы

  • Нет связанных вопросов^_^