Marzec
08
2010

Testowanie wtyczek Maven

Słowa kluczowe: , , , , | Kategorie: Java
No Gravatar

Automatyczne testowanie oprogramowania, do niedawna stanowiące najwyżej uzupełnienie dla ręcznych testów, dziś jest powszechną praktyką. Także idea programowania sterowanego testami (ang. Test-Driven Development) zyskuje coraz więcej zwolenników wśród programistów i kierowników – nie tylko w zespołach stosujących metodyki zwinne. Nierozsądnym byłoby pominąć testy przy tworzeniu wtyczki Maven.

Apache Maven narzuca ściśle określony cykl życia oprogramowania. Zaraz po bezbłędnej kompilacji, projekt musi pozytywnie przejść testy, aby mógł zostać połączony w pakiet, zainstalowany i opublikowany. Dzięki temu twórca oprogramowania może mieć pewność, że nie opublikuje programu, w którym wykryte zostały błędy.

pom.xml

Do uruchomienia konkretnego zadania (tzw. Mojo) wykorzystać można bibliotekę maven-plugin-testing-harness. Wprawdzie została ona skonstruowana w oparciu o JUnit, ale nie ma żadnych przeszkód do użycia jej w połączeniu z TestNG. Dlatego też do zależności projektu dodaję następujące biblioteki:

<dependencies>
  […]
  <dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>5.9</version>
    <classifier>jdk15</classifier>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.apache.maven.shared</groupId>
    <artifactId>maven-plugin-testing-harness</artifactId>
    <version>1.1</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Do uruchomienia testów najlepiej użyć Maven Surefire Plugin, działającego równie dobrze w połączeniu z JUnit oraz TestNG. W przypadku mojej wtyczki, wykorzystana została ta druga biblioteka, co oznacza konieczność wskazania pliku z konfiguracją uruchamiania testów:

<build>
  <plugins>
    […]
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.5</version>
      <configuration>
        <suiteXmlFiles>
          <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
        </suiteXmlFiles>
      </configuration>
    </plugin>
  </plugins>
</build>

Plik src/test/resources/testng.xml, zawierający ustawienia TestNG, przedstawia się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="LaTeX MetaPost Maven Plugin" verbose="1">
  <test name="LaTeX MetaPost">
    <packages>
      <package name="pl.info.czerwinski.latex.metapost"/>
    </packages>
  </test>
</suite>

Pliki Dia i MetaPost

Dla potrzeb testów użyłem trzech obrazów, które miałyby być osadzone w dokumencie LATEX:

src/test/latex/common/common.mp
dowolny plik MetaPost, wspólny dla wszystkich dokumentów,
src/test/latex/fooDir/foo.mp
dowolny plik MetaPost, znajdujący się w dokumencie fooDir,
src/test/latex/diaDir/diaFoo.dia
dowolny diagram Dia, znajdujący się w dokumencie diaDir.

Przykładowe pliki do testów można znaleźć tutaj.

src/test/resources/test-pom.xml

Aby skonfigurować uruchomienie testowanego zadania, użyję osobnego pliku test-pom.xml. Dzięki temu będę mógł sprawdzić, jak moja wtyczka działa w połączeniu z Apache Maven. Projekt testowy zawiera jedynie konfigurację dla latex-metapost-maven-plugin:

<?xml version="1.0" encoding="UTF-8"?>
<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>pl.info.czerwinski</groupId>
  <artifactId>latex-metapost-test</artifactId>
  <version>1.0-SNAPSHOT</version>

  <build>
    <plugins>
      <plugin>
        <groupId>pl.info.czerwinski</groupId>
        <artifactId>latex-metapost-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <configuration>
          <docsRoot>src/test/latex</docsRoot>
          <commonsDirName>common</commonsDirName>
          <latexBuildDir>target/latex</latexBuildDir>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

Oczywiście testy można rozszerzyć o zbudowanie dokumentu z osadzonymi obrazkami, ale na obecnym etapie ograniczę się do utworzenia właściwych plików .mps.

src/test/java/pl/info/czerwinski/latex/metapost/MetaPostCompilationMojoTest.java

Przypadki testowe są reprezentacją wymagań funkcjonalnych, stawianych przed tworzoną aplikacją. Dlatego pozwolę sobie przerywać opisy poszczególnych metod informacjami dotyczącymi obrazów MetaPost wymaganych przez LATEX.

Aby możliwym było wykorzystanie gotowego pliku POM, klasa testowa powinna dziedziczyć po AbstractMojoTestCase, pochodzącej z Maven Plugin Harness:

package pl.info.czerwinski.latex.metapost;

import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.File;

/**
 * MetaPost compilation goal test.
 * @author Sławomir Czerwiński
 * @version 1
 */
@Test
public class MetaPostCompilationMojoTest extends AbstractMojoTestCase {
}

Przed uruchomieniem którejkolwiek metody testowej, należy utworzyć obiekt, który będzie testowany. Wewnątrz setUpMojo wywołuję metodę setUp klasy AbstractMojoTestCase (linia 8), inicjującą wykorzystywane dalej funkcje. Tworzenie instancji Mojo odbywa się poprzez wyszukanie jej poprzez nazwę zadania (linie 14–15) z kontekstu wskazanego pliku POM (linie 10–12):

/**
 * Sets up tested MOJO.
 * @throws Exception if MOJO set up fails.
 */
@BeforeClass
public void setUpMojo() throws Exception {
    // Set up MOJO test case:
    super.setUp();
    // Get testing POM file:
    File testPom = new File(
            getBasedir(),
            "src/test/resources/test-pom.xml");
    // Lookup testing MOJO:
    mojo = (MetaPostCompilationMojo)
            lookupMojo("compile", testPom);
}

/** Tested MOJO. */
private MetaPostCompilationMojo mojo;

Pierwszy test dotyczyć będzie wstępnej konfiguracji Mojo oraz samego uruchomienia zadania. Jeżeli testowany obiekt w ogóle istnieje (linia 8), istnieć musi też katalog wskazany przez parametr docsRoot (linie 10–13). Następnie sprawdzam jeszcze wartość parametru commonsDirName (linie 14–18).

Uruchomienie zadania (linia 20) wiąże się z pewnym ryzykiem, wynikającym ze sposobu działania MetaPost. Wywołanie polecenia mpost bez parametrów uruchamia konsolę MetaPost, przez co działanie metody execute może się nigdy nie zakończyć. Z tego powodu, dla metody testowej ustawiłem dość duży limit czasu, wynoszący 30 sekund (linia 5):

/**
 * Tests compile goal execution.
 * @throws Exception if execution fails.
 */
@Test(timeOut = 30000)
public void testExecute() throws Exception {
    // Check if the mojo exists:
    assertNotNull("Tested MOJO", mojo);
    // Check parameters:
    assertTrue(
            "The documents root is a directory",
            ((File) getVariableValueFromObject(
                    mojo, "docsRoot")).isDirectory());
    assertEquals(
            "The commons directory",
            "common",
            (String) getVariableValueFromObject(
                    mojo, "commonsDirName"));
    // Execute MOJO:
    mojo.execute();
}

Sens przeprowadzania jakichkolwiek dalszych testów istnieje wyłącznie w przypadku pomyślnego uruchomienia testExecute. Dlatego w adnotacji @Test każdej z kolejnych metod ustawiłem atrybut dependsOnMethods.

Pliki .mp mogą być zarówno plikami pośrednimi, utworzonymi za pomocą polecenia dia, jak i plikami wejściowymi. Przy użyciu polecenia mpost są one kompilowane do jednego lub wielu plików z rozszerzeniami .1, .2 itd., będących de facto obrazami PostScript. Jednak do poprawnego osadzenia grafiki LATEX potrzebuje plików z rozszerzeniem .mps (pliki .1.mps itp. nie są dozwolone). Dlatego postanowiłem, aby każdy plik foo.N (gdzie N jest liczbą całkowitą) zamieniać na foo_N.mps.

Jako pierwszego, poszukuję pliku wynikowego dla src/test/latex/fooDir/foo.mp. Poprawnie działająca wtyczka powinna utworzyć plik o nazwie target/latex/fooDir/foo_1.mps (linie 7–8). Na końcu usuwam odnaleziony zasób (linia 10), aby następne wywołanie testów było miarodajne:

/**
 * Tests output file.
 */
@Test(dependsOnMethods = "testExecute")
public void testOutputFile() {
    // Check if the output file exists:
    File output = new File(getBasedir(), "target/latex/fooDir/foo_1.mps");
    assertTrue("Output file exists", output.exists());
    // Delete the output file:
    assertTrue("File deleted", output.delete());
}

Analogicznie testuję utworzenie pliku target/latex/diaDir/diaFoo_1.mpssrc/test/latex/diaDir/diaFoo.dia, co pozwala mi sprawdzić poprawność konwersji z formatu Dia do MetaPost:

/**
 * Tests Dia output file.
 */
@Test(dependsOnMethods = "testExecute")
public void testDiaOutputFile() {
    // Check if the dia output file exists:
    File diaOutput = new File(
            getBasedir(), "target/latex/diaDir/diaFoo_1.mps");
    assertTrue("Dia output file exists", diaOutput.exists());
    // Delete the dia output file:
    assertTrue("Dia file deleted", diaOutput.delete());
}

Nieco inaczej prezentuje się wymagany rezultat działania wtyczki dla pliku znajdującego się w katalogu src/test/latex/common. Plik common_1.mps powinien znaleźć się zarówno w katalogu target/latex/fooDir jak również target/latex/diaDir:

/**
 * Tests common output file.
 */
@Test(dependsOnMethods = "testExecute")
public void testCommonOutputFile() {
    // Check if the common output file exists in MetaPost target:
    File commonOutput = new File(
            getBasedir(), "target/latex/fooDir/common_1.mps");
    assertTrue("Common output file exists",
            commonOutput.exists());
    // Delete the common output file:
    assertTrue("Common file deleted", commonOutput.delete());
    // Check if the common output file exists in Dia target:
    File diaCommonOutput = new File(
            getBasedir(), "target/latex/diaDir/common_1.mps");
    assertTrue("Dia common output file exists",
            diaCommonOutput.exists());
    // Delete the Dia common output file:
    assertTrue("Dia common file deleted",
            diaCommonOutput.delete());
}

Muszę się jednak upewnić, że nie został utworzony katalog target/latex/common:

/**
 * Tests common directory output.
 */
@Test(dependsOnMethods = "testExecute")
public void testCommonDirectory() {
    // Check if common directory exists:
    File commonDir = new File(getBasedir(), "target/latex/common");
    assertFalse("Common output file exists", commonDir.exists());
}

Tak napisane testy gotowe są do uruchomienia.

Uruchomienie testów

Aby uruchomić automatyczne testy dowolnego projektu z użyciem Apache Maven, wystarczy wykonać polecenie mvn test. W tym wypadku spodziewany jest rezultat podobny do następującego:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
[info] Compiling MetaPost for LaTeX.

===============================================
LaTeX MetaPost Maven Plugin
Total tests run: 5, Failures: 3, Skips: 0
===============================================

Tests run: 5, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 1.075 sec <<< FAILURE!

Results :

Failed tests:
  testOutputFile(pl.info.czerwinski.latex.metapost.MetaPostCompilationMojoTest)
  testCommonOutputFile(pl.info.czerwinski.latex.metapost.MetaPostCompilationMojoTest)
  testDiaOutputFile(pl.info.czerwinski.latex.metapost.MetaPostCompilationMojoTest)

Tests run: 5, Failures: 3, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] There are test failures.

Zgodnie z założeniami uruchomienie Mojo nie spowodowało żadnego błędu i nie utworzyło katalogu target/latex/common. Brakuje jednak plików, które powinny powstać w wyniku działania wtyczki.

Podsumowanie

Rezultaty testów są prawidłowe dla pustej implementacji metody execute. Aby wszystkie testy zakończyły się sukcesem, należy odpowiednio uzupełnić klasę MetaPostCompilationMojo, której bieżąca wersja znajduje się w repozytorium. Jednak omówienie pełnej implementacji wtyczki pozostawię na inną okazję.

Następny wpis dotyczący wtyczek Maven zawierać będzie więcej informacji na temat tworzenia raportów, dokumentacji czy strony projektu.

Napisz Komentarz

*