AspecJUnit i maven

Błąd typograficzny w temacie zamierzony…

Dziś stanąłem przed poważnym problemem. Jak przetestować narzędzie do komunikacji z Google Translate zza proxy. Niby prosta rzecz. Wystarczy tylko przy odpalaniu mavena dać mu odpowiednie ustawienia. Tylko, że ja jestem cholernie leniwy (cnota programisty). Dlatego też poszukiwałem rozwiązania, które pozwoli mi zastąpić każdorazowe klepanie konfiguracji proxy.

Własny plugin = epic fail

Co tu dużo mówić. Straciłem 2 godziny bo zapomniałem, że JUnit odpala się w ramach odizolowanego środowiska co oznacza, że nie może przejmować ustawień w stylu System.setProperty(). W każdym bądź razie wiem już jak pisać i testować pluginy do mavena. Zawsze coś.

AspectJ to Malleus MaleficarumW

Idea wydaje się na pierwszy rzut granatu poroniona. Programowanie aspektowe jest w swej istocie dość skomplikowanym procesem, a w przypadku Javy to naprawdę wyższa szkoła jazdy na kucykach. Jednak jeżeli pomyślimy przez chwilę to okazuje się, że nie taki diabeł straszny. Szczególnie, że jeżeli weźmiemy pod uwagę fakt, że jeżeli źle coś napiszemy to okazuje się iż testy po prostu nie zadziałają.
Na czym polega myk? Otóż wystarczy, że wstrzykniemy kod przed każdym wykonaniem metody before(). W sumie nie ma znaczenia fakt, że wydłuży to testy. Testy są po to by mieć czas na kawę, a nie po to by były szybkie.

Praktyka

Klasa testująca wygląda w następujący sposób:

Listing 1. Nieszczęśliwy test

/**
 * 
 */
package pl.koziolekweb.translator.engine;

import junit.framework.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import pl.koziolekweb.translator.engine.googleimpl.Language;
import pl.koziolekweb.translator.engine.googleimpl.TranslatorImpl;

/**
 * @author koziolek
 * 
 * 
 */
// Info: $Id: TranslatorTest.java 161 2009-03-16 22:26:47Z bjkuczynski $
public class TranslatorTest {

	public static Translator translator;

	public static final String PL_TEXT = "nie";
	public static final String EN_TEXT = "no";
	public static final Language DEFAULT_LANGUAGE = Language.POLISH;

	@Test
	public void testTranslateToNativeFromAuto() throws Throwable {
		Assert.assertEquals(PL_TEXT, translator.translate(PL_TEXT));
	}

	@Test
	public void testTranslateToNativeFromSet() throws Throwable {
		Assert.assertEquals(PL_TEXT, translator.translate(EN_TEXT,
				Language.ENGLISH));
	}

	@Test
	public void testTranslateToSetFromSet() throws Throwable {
		Assert.assertEquals(EN_TEXT, translator.translate(PL_TEXT,
				Language.POLISH, Language.ENGLISH));
	}

	@Before
	public void before() {
		translator = new TranslatorImpl(DEFAULT_LANGUAGE);
	}

	@After
	public void after() {
		translator = null;
	}
}

Metoda translate() wykonuje m.n. połączenie z jakimś zdalnym serwisem tłumaczącym. W tym przypadku jest to Google Translate. W aplikacji będą odpowiednie ustawienia, ale w czasie testów nie chcemy ich tworzyć. Jednocześnie nie chcemy ingerować w kod testów ani kombinować z dynamicznym ładowaniem ustawień w testach. Dlatego też piszemy aspekt, który rozwiązuje nam ten problem. Oto on:

Listing 2. Nieszczęśliwy aspekt

package pl.koziolekweb.translator.engine;

import java.io.FileInputStream;
import java.util.Properties;

public aspect ProxyConfig {

	private String proxySet;
	private String proxyHost;
	private String proxyPort;

	public ProxyConfig() {
		Properties properties = new Properties();
		try{
		properties.load(new FileInputStream(
				"./src/test/resources/proxy.properties"));
		}
		catch(Exception e){
			throw new RuntimeException(e);
		}
		proxySet = properties.getProperty("proxySet");
		proxyHost = properties.getProperty("proxyHost");
		proxyPort = properties.getProperty("proxyPort");
	}

	pointcut config():
		execution( void pl.koziolekweb.translator.engine.*Test.before());

	before(): config() {
		System.out.println("[ASPECT] Configure Proxy: ");
		System.out.println("[ASPECT] Use Proxy   : " + proxySet);
		System.out.println("[ASPECT] Proxy Host  : " + proxyHost);
		System.out.println("[ASPECT] Proxy Port  : " + proxyPort);

		System.getProperties().put("proxySet", proxySet);
		System.getProperties().put("proxyHost", proxyHost);
		System.getProperties().put("proxyPort", proxyPort);
		System.out.println("[ASPECT] Proxy Configured ");
	}
}

Jak widać za trudne to nie jest. Nie powinno się też jakoś magicznie wywalać. Kolejny krok to konfiguracja w pom.xml kompilatora AspectJ.

Listing 3. Nieszczęśliwy pom.xml

<profiles>
	<profile>
		<id>proxy</id>
		<build>
			<plugins>
				<plugin>
					<groupId>org.codehaus.mojo</groupId>
					<artifactId>aspectj-maven-plugin</artifactId>
					<version>1.1</version>
					<configuration>
						<source>1.5</source>
						<target>1.5</target>
					</configuration>
					<executions>
						<execution>
							<id>test</id>
							<phase>test-compile</phase>
							<goals>
								<goal>test-compile</goal>
							</goals>
						</execution>
					</executions>
				</plugin>
			</plugins>
		</build>
	</profile>
</profiles>

I bangla.

Podsumowanie

Dlaczego tak kombinuję? Przecież skoro mam możliwość konfiguracji proxy w aplikacji to mogę wywołać metodę i skonfigurować je w testach. Tyle tylko, że jeżeli tak zrobię to uzależniam test od mechanizmu testowanego. Co jeżeli ustawianie proxy nie działa? Jeżeli tamte testy padną? Tu mam możliwość sprawdzenia czy wszystko działa za pomocą odpowiednich narzędzi. Odpowiedni dobór narzędzia umożliwił mi w tym przypadku maksymalizację zysków przy minimalnych kosztach. Istotne jest to, że konfigurację testów przerzucam poza testy. Całość jest nieinwazyjna i nie zakłóca „logiki biznesowej” testów.

//
Poza tym szukałem pretekstu by poznać AspectJ i nauczyć się pisania pluginów do mavena.

4 myśli na temat “AspecJUnit i maven

  1. Witaj Koziołku,

    ja rozumiem że na tym przykłądzie uczyłeś się AspectJ/pluginów, ale doprawdy poprawniej byłoby tak (pseudokod TestNG):

    @Test
    public class TranslatorTest {

    public void setProxySettings() {

    }

    @Test(dependsOn = setProxySettings)
    public void testTranslateToNativeFromAuto() {

    }

    @Test(dependsOn = setProxySettings)
    public void testTranslateToNativeFromSet()

    }

    itd.

    }

    Jak się ustawianie proxy wywali, to wszystkie testy zrobią SKIP i sprawa jest jasna co nie działało.

    AspectJ to mocna rzecz, ale to chyba nie jest najlepsze miejsce na jego uzywanie.

    pozdrawiam
    Tomek Kaczanowski

  2. Hej Tomku, miło gościć na blogu. Rzeczywiście uczyłem się na tym aspektów, a sam przykład no cóż… był spektakularnie widoczny jeżeli nie poszedł.
    Co do TestNG to od pewnego czasu macam się z tym narzędziem i jedyną przyczyną dla której go nie stosuję jeszcze jest konieczność szkolenia kilku starszych datą programistów, a ci dopiero co chwycili samą ideę testów. JUnit jest znacznie prostszy i na początku przyjaźniejszy, ale co się odwlecze 😉

  3. witaj,

    ja z rzadka używam jUnit, ale zdaje mi się że od kilku wersji tam już też można podobny efekt uzyskać.


    pozdrawiam
    Tomek Kaczanowski

  4. Powiem szczerze, że nie kojarzę, żeby w JUnit dało się wiązać testy tak jak w TestNG. Znaczy są testsuity, ale są dość toporne.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax