jUnit + Reflections by zaoszczędzić kilka chwil

Java Komentarze (0) »

Kilka razy (… ok może kilkanaście ;) ) zdarzyło mi się napisać klasy reprezentujące encje Hibernate i zapomnieć dopisać je do XML’a z mappingiem. Te niedopatrzenie wychodzi na jaw w momencie deploy’u aplikacji. No ale żeby wiedzieć co jest grane trzeba zajrzeć do logów. Wiadomo nie zajmuje to może jakoś specjalnie wiele czasu ale na pewno rozbija rytm pracy i nie potrzebnie zmusza do przeszukiwania logów.

Plan jest taki: Piszemy jUnitowy test, który sprawdzi czy czasem nie zapomnieliśmy o umieszczeniu wpisu z mappingiem w XML’u dla którejś z encji.

Każda encja jest oznaczona adnotacją @Entity (jeśli używamy adnotacji oczywiście), więc najpierw musimy znaleźć wszystkie klasy oznaczone tą adnotacją, a potem sprawdzić czy odpowiedni wpis znajduje się w pliku XML.

Aby znaleźć wszystkie klasy oznaczone @Entity najlepszym rozwiązaniem wydaje się użycie mechanizmu refleksji. Ja poszedłem na łatwiznę i wykorzystałem bibliotekę Reflections - która dostarcza wysokopoziomowego API.

OK w takim razie w jaki sposób wyszukać wszystkie klasy adnotowane @Entity ?

 Set> set = new Reflections(pakiet).getTypesAnnotatedWith(Entity.class);

W ten sposób zyskujemy dostęp do wszystkich klas adnotowanych @Entity znajdujących się w pakiecie pl.kedziorski. Z obiektu Class łatwo wyciągnąć pełną nazwę klasy, którą umieszcza się w pliku z mappingami.

  clazz.getCanonicalName();

Teraz wystarczy już tylko sprawdzić czy plik XML z mappingiem zawiera znalezioną nazwę.

private Boolean isContainingMapping(String className){
		BufferedReader br;
		try {
			br = new BufferedReader(new FileReader(hibernateMappingFilePath));
 
			String line;
			while ((line = br.readLine()) != null) {
			   if(line.contains(className)) {
				   br.close();
				   return true;
			   }
			}
			br.close();
			return false;
		} catch (IOException e) {
			return false;
		}
	}

Akurat tu sprawdzam linijka po linijce - można by wykorzystać fakt, że mamy do czynienia z plikiem XML i zrobić to znacznie ładniej, ale nie o to mi chodzi. Całość testu, który zabezpiecza zapominalskich przed niepotrzebnym wrzucaniem builda, który i tak się nie uruchomi wygląda następująco:

 import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Set;
 
import javax.persistence.Entity;
 
import org.junit.Assert;
import org.junit.Test;
import org.reflections.Reflections;
 
public class HibernateMappingsTest {
	private static final String hibernateMappingFilePath = "src/main/webapp/WEB-INF/database/DataSource.xml";
	private static final String pakiet = "pl.kedziorski";
 
	@Test
	public void testMappings(){
		Set> set = new Reflections(pakiet).getTypesAnnotatedWith(Entity.class);
		for (Class<!--?--> clazz : set) {
			Assert.assertTrue(isContainMapping(clazz.getCanonicalName()));
		}
		Assert.assertTrue(true);
	} 
 
	private Boolean isContainMapping(String className){
		BufferedReader br;
		try {
			br = new BufferedReader(new FileReader(hibernateMappingFilePath));
 
			String line;
			while ((line = br.readLine()) != null) {
			   if(line.contains(className)) {
				   br.close();
				   return true;
			   }
			}
			br.close();
			return false;
		} catch (IOException e) {
			return false;
		}
	}
}

W podobny sposób wykorzystałem Reflections do wymuszania pewnej konwencji w projekcie - otóż każda moja encja ma statyczne pole Null , które zawiera NullObject tej klasy. Dodatkowo implementuje interfejs NullObject i metodę isNull(). Wykorzystuje te pole m.in w generycznym DAO i ważne jest aby było ono w każdej encji - no ale niestety czasami zdarza mi się o nim zapomnieć (… czasami przez kopiuj-wklej pole Null ma nie ten typ co powinno) - i przed tym również można dość łatwo zabezpieczyć się korzystając z mechanizmu reflekcji i testu. Kod poniżej już bez omawiania.

import java.lang.reflect.Field;
import java.util.Set;
 
import org.junit.Assert;
import org.junit.Test;
import org.reflections.Reflections;
 
import pl.kedziorski.NullObject;
 
public class NullPropertyTest {
 
	private static final String pakiet = "pl.kedziorski";
 
	@Test
	public void testNullPropertyPresence() throws SecurityException, NoSuchFieldException{
		Set&gt; set = new Reflections(pakiet).getSubTypesOf(NullObject.class);
		for (Class<!--? extends NullObject--> clazz : set) {
			clazz.getField("Null");
		}
		Assert.assertTrue(true);
	}
 
	@Test
	public void testNullPropertyType() throws SecurityException, NoSuchFieldException{
		Set&gt; set = new Reflections(pakiet).getSubTypesOf(NullObject.class);
		for (Class<!--? extends NullObject--> clazz : set) {
			if(!clazz.getSimpleName().startsWith("Null")){
				Field field = clazz.getField("Null");
				Assert.assertTrue("Złamana konwencja NullObject!! Niepoprawny typ pola 'Null'. Klasa: "+clazz.getCanonicalName(),field.getDeclaringClass().equals(clazz));
			}
		}
	}
	@Test
	public void testNullPropertyValueNotSimpleNull() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Set&gt; set = new Reflections(pakiet).getSubTypesOf(NullObject.class);
		for (Class<!--? extends NullObject--> clazz : set) {
			if(!clazz.getSimpleName().startsWith("Null")){
				Field field = clazz.getField("Null"); 
 
				Assert.assertNotNull("Złamana konwencja NullObject!! Pole 'Null' == null. Klasa: "+clazz.getCanonicalName(), field.get(null));
			}
		}
	}
 
	@Test
	public void testNullPropertyValue() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
		Set&gt; set = new Reflections(pakiet).getSubTypesOf(NullObject.class);
		for (Class<!--? extends NullObject--> clazz : set) {
			if(!clazz.getSimpleName().startsWith("Null")){
				Field field = clazz.getField("Null");
				Assert.assertEquals("Złamana konwencja NullObject!! Klasa: "+clazz.getCanonicalName(),"Null"+clazz.getSimpleName(), field.get(null).getClass().getSimpleName());
			}
		}
	}
}

Mam nadzieje że komuś się przyda - jeśli ktoś ma jakiś pomysł przed czym jeszcze można by się zabezpieczyć w ten sposób - będę wdzięczny za komentarz. Jeśli jest jakaś ciekawsza metoda to również proszę o wpis z namiarem w komentarzu;)

Silnik: Wordpress - Theme autorstwa N.Design Studio. Spolszczenie: Adam Klimowski.
RSS wpisów RSS komentarzy Zaloguj








2zł Nordic Gold