Spring profiles a použití v testech

Po 3 letech dělám větší refaktoring na našem direct mailingovém modulu a jako první jsem se rozhodl povýšit verze knihoven a zrefaktorovat JUnit testy, které jsou tam ještě psané ve stylu JDK 1.4.

V souvislosti s tím jsem samozřejmě přepracoval formu testů z dědičné hierarchie na anotace, které byly představeny poprvé ve Spring 3.X (pokud se nepletu). A tu jsem zjistil, že mám drobný problém - v původní verzi mého kódu jsem využíval dynamické kompozice Springového kontextu k tomu, abych stejné integrační testy pustil proti různým implementacím úložišť dat (paměť, MySQL databáze, Oracle databáze ...). V aktuální verzi Springu se ale v anotaci @ContextConfiguration uvádí statický výčet konfiguračních XML a to situaci komplikuje.

Řešení této situace je samozřejmě možné i v aktuálním Springu a řešení je nyní dispozici víc. V tomto článku bych chtěl pro vás i pro své kolegy popsat to, co se líbí mě ...

Varování! V tomto článku nenajdete nic novějšího, než je v dokumentaci Springu - tento článek má jediný význam - upozornit na zajímavé novinky ve Spring 3.1+ pro vývojáře, kteří (stejně jako já) začínali na starém Springu a některých novinek si v záplavě zpráv nestihli všimnout.

K řešení výše uvedeného problému bych mohl sice využít možnost dědičné kompozice aplikačních kontextů, ale musel bych  kopírovat umístění konfiguračních souborů pro jednotlivá úložiště, což je poměrně nepraktické (zvlášť, když je těchto testovacích tříd hodně). Koukněte se sami:

Base test:

@ContextConfiguration(
  locations = {
    "classpath:/META-INF/lib_mail/spring/test-context.xml"
  }
)
public abstract class AbstractMessageStorageTestCase {
    @Autowired private MessageStorage messageStorage;
    @Test
    public void shouldPersistSimpleMessageBatch() {
       ....
    }
    @Test(expected = RecipientNotDefinedException.class)
    public void shouldFailToPersistMessageBatchWithoutRecipients() {
       ....
    }
}

Konkrétní test A:

@ContextConfiguration(
  locations = {
    "classpath:/META-INF/lib_mail/spring/mysql-datasource.xml",
    "classpath:META-INF/lib_mail/spring/mail-database-dao-config.xml"
  }
)
public class MysqlMessageStorageTest extends AbstractMessageStorageTestCase {
}

Konkrétní test B:

@ContextConfiguration(
  locations = {
    "classpath:/META-INF/lib_mail/spring/memory-dao-config.xml"
  }
)public class MemoryMessageStorageTest extends AbstractMessageStorageTestCase {
}

Nehledě na to, že jsem si už nějakou dědičnost v testech zavedl a ta mi celou věc ještě více komplikuje.

Elegantnějším řešením je využít poměrně zajímavou novinku - tzv. profily. Ty můžete využít nejen pro testy ale i v produkčním kódu (což je věc, kterou určitě začnu praktikovat). V mém případě jsem si hlavním konfiguračním souboru pro testy udělal následující rozdělení:

Test-config.xml

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- sdílené konfigurace pro všechny testy -->
    <import resource="classpath:/META-INF/lib_mail/spring/mail-config.xml"/>
    <import resource="classpath:/META-INF/lib_mail/spring/mail-test-basis.xml"/>
    <!-- specifické nastavení pro profil database -->
    <beans profile="database">
        <!-- tento konfigurační soubor je sdílený pro všechny DB implementace -->
        <import resource="classpath:META-INF/lib_mail/spring/mail-database-dao-config.xml"/>
        <beans profile="mysql">
            <!-- pro mysql ale nahazujeme jiný JDBC datasource -->
            <import resource="datasource-mysql.xml"/>
        </beans>
        <beans profile="oracle">
            <!-- pro oracle ale nahazujeme jiný JDBC datasource -->
            <import resource="datasource-oracle.xml"/>
        </beans>
    </beans>
    <!-- specifické nastavení pro profil memory -->
    <beans profile="memory">
        <import resource="classpath:META-INF/lib_mail/spring/mail-memory-dao-config.xml"/>
    </beans>
</beans>

Třída Base test zůstává potom úplně stejná. V jednotlivých testovacích třídách, které mají integračně testovat odlišné implementace úložišť nyní pouze aktivuji profily, které odpovídají konkrétním implementacím:

Konkrétní test A:

@ActiveProfiles({"database", "mysql"})
public class MysqlMessageStorageTest extends AbstractMessageStorageTestCase {
}

Konkrétní test B:

@ActiveProfiles({"memory"})
public class MemoryMessageStorageTest extends AbstractMessageStorageTestCase {
}

Výsledkem je přehledný kód, kde máte všechno pohromadě a přehledně na jednom místě, takže pochopení logiky a použití je přímočaré.