2013-06-26 28 views
5

In questo momento abbiamo un progetto che crea due lavori. 1) È la versione standard con test unitari. 2) sono i test di integrazione. Essi funzionano in questo modo:Come posso utilizzare Jenkins per eseguire i miei test di integrazione in parallelo?

  1. costruire l'intero progetto, test di unità di esecuzione, avviare il processo di test di integrazione
  2. costruire l'intero progetto, distribuirlo al server di integrazione, test di integrazione lato client eseguito su server di integrazione

Il problema è il passaggio 2) ora occorre più di un'ora per essere eseguito e mi piacerebbe parallelizzare i test di integrazione in modo che richiedano meno tempo. Ma non sono esattamente sicuro di come posso/dovrei farlo. Il mio primo pensiero è che io possa avere due step 2) s in questo modo:

  1. lavoro costruire l'intero progetto, test di unità di esecuzione, avviare test di integrazione
  2. costruire l'intero progetto, distribuirlo al server1 integrazione, client eseguire test di integrazione lato contro server1 integrazione
  3. costruire l'intero progetto, implementare al server2 integrazione, test di integrazione lato client corsa contro server2 integrazione

Tuttavia, come si eseguono metà dei test di integrazione sul server di integrazione1 e l'altra metà sul server di integrazione2? Sto usando Maven, quindi potrei probabilmente capire qualcosa con failsafe e un complesso include/esclude pattern. Ma sembra qualcosa che richiederebbe molto sforzo per mantenere. Ad esempio: quando qualcuno aggiunge una nuova classe di test di integrazione, come posso assicurarmi che venga eseguita su uno dei due server? Lo sviluppatore deve modificare i modelli di esperti?

risposta

2

Ho trovato this great article su come eseguire questa operazione, ma offre un modo per farlo in codice Groovy. Ho praticamente seguito questi passaggi, ma non ho scritto il codice per distribuire i test in modo uniforme per durata. Ma questo è ancora uno strumento utile, quindi lo condividerò.

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

Il ClasspathFinderFactory viene utilizzato per trovare tutte le classi di prova che corrispondono al modello .*IntegrationTest.

Faccio N lavori e tutti eseguono questo Runner ma tutti usano valori diversi per la proprietà di sistema node.number, quindi ogni lavoro esegue una serie diversa di test. Questo è come il plugin fail-safe si presenta:

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

Il ClasspathFinderFactory viene da

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

penso che ci dovrebbe essere qualche plugin di Jenkins per questo, ma non sono stato in grado di trovare uno. Qualcosa che è vicino è il Parallel Test Executor, ma non penso che questo faccia la stessa cosa di cui ho bisogno. Sembra che esegua tutti i test su un singolo lavoro/server anziché su più server. Non fornisce un modo ovvio per dire "esegui questi test qui e quei test lì".

+0

> Sembra che esegua tutti i test su un singolo lavoro/server invece di più server. Quando il processo di test è configurato su "Esegui build simultanei, se necessario", le esecuzioni potrebbero essere eseguite su un altro nodo da Jenkins. –

1

Credo che già trovato una soluzione ormai, ma lascio un percorso per gli altri che ti aprono la pagina che chiede la stessa domanda:
parallelo plug prova esecutore:
"Questo plugin aggiunge una nuova builder che consente di eseguire in modo semplice test definiti in un lavoro separato in parallelo, ottenendo che Jenkins guardi il tempo di esecuzione del test dell'ultima esecuzione, divida i test in più unità di dimensioni approssimativamente uguali, quindi li esegua in parallelo. "
https://wiki.jenkins-ci.org/display/JENKINS/Parallel+Test+Executor+Plugin

+0

Sarebbe meglio se potessi includere anche la soluzione reale nella tua risposta nel caso in cui questo link si rompesse in futuro. – Christina

0

Sì, Parallel test esecutore è un plugin fresco se hai 2 schiavo o uno slave con 8 esecutore perché il questo plugin sulla base di "prove di splitting", così ad esempio: di dividere i test JUnit in 4 differenti array, questi array verranno eseguiti su 4 diversi executor su quello slave che cosa hai specificato. Spero che tu abbia capito: D, dipende dal numero di esecutori su quello slave in cui vuoi eseguire test paralleli o dovresti ridurre i test divisi contando fino a 2 da 4.

Problemi correlati