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.