Mariusz Prowaźnik

o programowaniu w Javie, Scali i Clojure.


Jak rozdzielić testy jednostkowe i integracyjne (z wykorzystaniem JUnit i Maven)

Jakiś czas temu pisałem o użyciu kategorii w testach JUnit. Możliwość oznaczenia testu kategorią jest przydatna, pod warunkiem, że w jakiś sposób Maven, wykonując testy, będzie w stanie je rozróżnić i wykonać tylko testy określonej kategorii, bo jednak ręczne dodawanie testów do zestawów (TestSuite) mija się z celem. Na szczęście taka konfiguracja Maven'a jest to możliwa.

Ok, mamy dwa interfejsy, które będą markerami dla naszych testów:
package com.evojava;
public interface IntegrationTest {}
package com.evojava;
public interface UnitTest {}
Testy niech będą następujące:
package com.evojava;

import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(UnitTest.class)
public class SmallTest {

 @Test
 public void test() {
  System.out.println("SmallTest");
 }
}
package com.evojava;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(IntegrationTest.class)
public class BigTest {

 @Test
 public void test() {
  System.out.println("BigTest");
 }
}

Za pomocą wtyczki Surefire, możemy skonfigurować projekt tak by uruchamiały się testy oznaczone określoną kategorią. Przykładowy plik pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.evojava</groupId>
 <artifactId>categories</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>categories</name>
 <description>categories</description>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.10</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.11</version>
    <configuration>
     <groups>com.evojava.UnitTest</groups>
    </configuration>
    <dependencies>
     <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>2.12</version>
     </dependency>
    </dependencies>
   </plugin>
  </plugins>
 </build>
</project>
Wykonujemy: mvn clean package
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Concurrency config is parallel='none', perCoreThreadCount=true, threadCount=2, useUnlimitedThreads=false
Running com.evojava.SmallTest
SmallTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Teraz wystarczy zmienić konfigurację, by przy wykonaniu mvn integration-test uruchomiły się pozostałe testy. Odpowiedni fragment pliku pom.xml:
<plugin>
 <artifactId>maven-surefire-plugin</artifactId>
 <version>2.11</version>
 <executions>
  <execution>
   <id>default-test</id>
   <phase>test</phase>
   <goals>
    <goal>test</goal>
   </goals>
   <configuration>
    <groups>com.evojava.UnitTest</groups>
   </configuration>
  </execution>
  <execution>
   <id>integration-tests</id>
   <phase>integration-test</phase>
   <goals>
    <goal>test</goal>
   </goals>
   <configuration>
    <excludedGroups>com.evojava.UnitTest</excludedGroups>
   </configuration>
  </execution>
 </executions>
 <dependencies>
  <dependency>
   <groupId>org.apache.maven.surefire</groupId>
   <artifactId>surefire-junit47</artifactId>
   <version>2.12</version>
  </dependency>
 </dependencies>
</plugin>
Wtedy, po uruchomieniu mvn clean integration-test
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Concurrency config is parallel='none', perCoreThreadCount=true, threadCount=2, useUnlimitedThreads=false
Running com.evojava.BigTest
BigTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec

Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Na koniec krótkie wyjaśnienie do tej konfiguracji.
  • Zostały skonfigurowane dwa uruchomienia testów: <id>default-test</id>, <id>integration-tests</id>.

  • default-test został przypięty do fazy test, integration-test do fazy integration-test.

  • W domyślnym cyklu budowania są między innymi fazy: validate, compile, test, package, integration-test, verify, install, deploy. Po mvn test będą uruchomione fazy do test włącznie, po mvn integration-test do integration-test włącznie (czyli po drodze również fazę test). Więcej na ten temat

  • default-test uruchamia tylko testy oznaczone kategorią UnitTest, integration-test wszystkie oprócz oznaczonych kategorią UnitTest. W rezultacie mvn test uruchamia testy jednostkowe, mvn integration-test wszystkie testy (bo faza test również się wykona)

  • Nazwa default-test jest istotna, bo jeśli nie skonfigurujemy jawnie tego uruchomienia, uruchomi domyślnie wszystkie testy

  • Jeśli w integration-test mają uruchomić się tylko testy oznaczone kategorią IntegrationTest, a nie wszystkie oprócz UnitTest, wystarczy zmienić:
    <configuration>
      <excludedGroups>com.evojava.UnitTest</excludedGroups>
    </configuration>
    na:
    <configuration>
      <groups>com.evojava.IntegrationTest</groups>
    </configuration>
    Akurat w tym przypadku na jedno wychodzi, ale jeśli pojawią kolejne kategorie, to już będzie różnica.
Kod można ściągnąć z https://evo-java.googlecode.com/svn/maven-int-test-example/