2013-06-26 12 views
5

W tej chwili mamy projekt, który opiera się na dwóch zadaniach. 1) Czy standardowa wersja z testami jednostkowymi. 2) to testy integracyjne. Działają one tak:Jak używać Jenkinsa do równoległego uruchamiania testów integracji?

  1. zbudować cały projekt, prowadzone testy jednostkowe, rozpocząć pracę testową integracja
  2. zbudować cały projekt, wdrożyć go do serwera integracyjnego, uruchom klienta testy integracyjne boku na serwerze integracyjnym

Problem to krok 2) teraz trwa ponad godzinę, a ja chciałbym zrównoleglić testy integracyjne, aby zajmowały one mniej czasu. Ale nie jestem do końca pewien, jak mogę/powinienem to zrobić. Moją pierwszą myślą jest to, że mogę mieć dwa krok 2) s tak:

  1. zadanie zbudować cały projekt, przeprowadzanie testów jednostkowych, start testu integracji
  2. zbudować cały projekt, wdrożyć go do serwer1 integracji, uruchom klienta testy integracyjne bokiem integracji serwer1
  3. budować cały projekt, wdrażanie go do server2 integracji, uruchom klienta testy integracyjne bokiem integracji server2

Ale jak mogę uruchomić połowę testów integracyjnych na serwerze integracyjnym1, a drugą na serwerze integracyjnym2? Używam maven, więc prawdopodobnie mógłbym znaleźć coś z failsafe, a kompleks zawiera/wyklucza wzór. Ale to brzmi jak coś, co wymagałoby wiele wysiłku, aby utrzymać. EG: kiedy ktoś dodaje nową klasę testu integracji, jak mogę się upewnić, że zostanie uruchomiona na jednym z dwóch serwerów? Czy programista musi zmodyfikować wzorce mavenów?

Odpowiedz

2

Znalazłem this great article o tym, jak to zrobić, ale daje to możliwość zrobienia tego w kodzie Groovy. W zasadzie podążałem za tymi krokami, ale nie napisałem kodu, aby rozprowadzić testy równomiernie według czasu trwania. Ale to wciąż jest przydatne narzędzie, więc podzielę się tym.

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; 
    } 
} 

The ClasspathFinderFactory służy do znalezienia wszystkich klas testowych, które pasują do wzorca .*IntegrationTest.

Wykonuję N zadań i wszystkie one wykonują to Runner, ale wszystkie one używają różnych wartości dla właściwości systemowej node.number, więc każde zadanie wykonuje inny zestaw testów. Jest to, jak wygląda plugin failsafe:

 <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 pochodzi z

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

myślę, że powinien być jakiś plugin Jenkins za to, ale nie udało się znaleźć. Coś, co jest blisko, to Parallel Test Executor, ale nie sądzę, że robi to to samo, czego potrzebuję. Wygląda na to, że uruchamia wszystkie testy na jednym zadaniu/serwerze zamiast na wielu serwerach. Nie zapewnia to oczywistego sposobu, aby powiedzieć: "uruchom tutaj te testy i testy tam".

+0

> Wygląda na to, że uruchamia wszystkie testy jedno zadanie/serwer zamiast wielu serwerów. Gdy zadanie testowe jest skonfigurowane do "Wykonaj kompozycje współbieżne, jeśli to konieczne", przebiegi mogą być uruchamiane w innym węźle przez Jenkinsa. –

1

Wierzę, że już znalazł rozwiązanie teraz, ale zostawię ścieżkę dla innych, którzy będą otwierane strony zadaje to samo pytanie:
równoległe badanie wykonawca wtyczki:
„Ta wtyczka dodaje nowy Konstruktor, który pozwala na łatwe wykonywanie testów zdefiniowanych w oddzielnym zadaniu równolegle, dzięki temu, że Jenkins sprawdza czas wykonania ostatniego testu, dzieli testy na kilka mniej więcej równych rozmiarów, a następnie wykonuje je równolegle. "
https://wiki.jenkins-ci.org/display/JENKINS/Parallel+Test+Executor+Plugin

+0

Najlepiej byłoby, gdyby w odpowiedzi można było uwzględnić faktyczne rozwiązanie, na wypadek gdyby ten link został zerwany w przyszłości. – Christina

0

Tak Równoległe badania Wykonawca jest fajny plugin, jeśli masz 2 niewolnika ani jednego niewolnika z 8 wykonawca ponieważ ten plugin na podstawie testów „rozszczepienie” tak np podzielić swoje testy JUnit w 4 różnych array, te tablice będą działały na 4 różnych executorach tego slave'a, które określiłeś. Mam nadzieję, że to dostałeś: D, to zależy od liczby executorów na tym niewolniku, gdzie chcesz przeprowadzić równoległe testy lub powinieneś zmniejszyć liczbę testów dzielonych do 2 z 4.

Powiązane problemy