También pueden echar un vistazo a testUtils, es una libreria interesante que les ayudara a realizar los test unitarios de una manera más elegante y sin escribir tanta redundancia de código:
package com.hector;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import org.junit.Assert;
import org.junit.Test;
import org.powermock.reflect.Whitebox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test automatique des getter/setter. A voir si on s'en sert.
*/
public class BeansAutomatedTest {
/** logger */
private static final Logger LOGGER = LoggerFactory
.getLogger(BeansAutomatedTest.class);
/** prefixes possibles pour les noms des getters */
public static final String[] POSSIBLE_GETTER_PREFIXES = { "get", "is",
"has" };
/** prefixes possibles pour les noms des setters */
private static final String[] POSSIBLE_SETTER_PREFIXES = { "set" };
/**
* map des implémentations à utiliser pour instancier des interfaces. Ex:
* List --> ArrayList
*/
private static final Map <Class <?>, Class <?>> IMPLEMENTATIONS_TO_USE = new HashMap <Class <?>, Class <?>>();
/**
* map des wrapper de types primitifs à utiliser pour tester les
* getter/setter sur des Integer, Boolean...
*/
private static final Set <Class <?>> BOXED_PRIMITIVES = new HashSet <Class <?>>();
/**
* map des primitifs à utiliser pour tester les getter/setter sur des types
* primitifs int, boolean...
*/
private static final Map <Class <?>, Object> PRIMITIVES_TO_USE = new HashMap <Class <?>, Object>();
/** map des tableaux de champs à ignorer par classe */
private static final Map <String, List <String>> IGNORED_FIELDS = new HashMap <String, List <String>>();
/** list des classes ignorées */
private static final List <String> IGNORED_BEANS = new ArrayList <String>();
private static enum TypeTest {
Entities, Representations, Dto, Vo
}
static {
IMPLEMENTATIONS_TO_USE.put(List.class, ArrayList.class);
IMPLEMENTATIONS_TO_USE.put(Set.class, HashSet.class);
IMPLEMENTATIONS_TO_USE.put(Collection.class, ArrayList.class);
IMPLEMENTATIONS_TO_USE.put(Map.class, HashMap.class);
BOXED_PRIMITIVES.add(Integer.class);
BOXED_PRIMITIVES.add(Long.class);
BOXED_PRIMITIVES.add(Character.class);
BOXED_PRIMITIVES.add(Double.class);
BOXED_PRIMITIVES.add(Float.class);
BOXED_PRIMITIVES.add(Boolean.class);
BOXED_PRIMITIVES.add(BigDecimal.class);
PRIMITIVES_TO_USE.put(int.class, 0);
PRIMITIVES_TO_USE.put(Integer.class, 0);
PRIMITIVES_TO_USE.put(long.class, 0L);
PRIMITIVES_TO_USE.put(Long.class, 0L);
PRIMITIVES_TO_USE.put(char.class, '\0');
PRIMITIVES_TO_USE.put(Character.class, '\0');
PRIMITIVES_TO_USE.put(boolean.class, true);
PRIMITIVES_TO_USE.put(Boolean.class, Boolean.TRUE);
PRIMITIVES_TO_USE.put(float.class, 0f);
PRIMITIVES_TO_USE.put(Float.class, 0f);
PRIMITIVES_TO_USE.put(double.class, 0d);
PRIMITIVES_TO_USE.put(Double.class, 0d);
PRIMITIVES_TO_USE.put(BigDecimal.class, BigDecimal.ZERO);
// Les champs ci-dessous sont modifiés à leur set ou à leur get
// TODO tester ces getters/setters unitairement
IGNORED_FIELDS
.put("com.hector.initialization.portefeuilledecommandes.PortefeuilleDeCommandesDto",
Arrays.asList(new String[] { "paysGamme",
"paysCommerce", "paysProgramme" }));
IGNORED_FIELDS
.put("com.hector.batch.referentiels.produit.bcv.BcvRequestHeaderDto",
Arrays.asList(new String[] { "header" }));
IGNORED_FIELDS.put(
"com.hector.domain.centredemontage.JourDeProduction",
Arrays.asList(new String[] { "dateJourProd" }));
IGNORED_FIELDS
.put("com.hector.rest.commande.RechercheCommandePlanifieRepresentation",
Arrays.asList(new String[] { "familles" }));
IGNORED_FIELDS
.put("com.hector.cycleprogramme.AppariementRepresentation",
Arrays.asList(new String[] { "alerte" }));
IGNORED_FIELDS
.put("com.hector.rest.cycleprogramme.CycleProgrammeRepresentation",
Arrays.asList(new String[] { "numSeqAnnee" }));
IGNORED_FIELDS
.put("com.hector.rest.seuilsAlerteventes.SeuilsAlerteVentesRepresentation",
Arrays.asList(new String[] { "codePcom" }));
IGNORED_FIELDS
.put("com.hector.rest.traitement.TraitementDeDefilementRepresentation",
Arrays.asList(new String[] { "annee", "Numordre" }));
IGNORED_FIELDS
.put("com.hector.rest.centredemontage.DifferentielcalendrierRepresentation",
Arrays.asList(new String[] { "dateJourProdstring" }));
// Beans ignorés
// TODO tester ces beans unitairement
IGNORED_BEANS
.add("com.hector.batch.diffusion.evn.EVNRequestFooterDto");
IGNORED_BEANS
.add("com.hector.batch.diffusion.evn.EVNRequestHeaderDto");
IGNORED_BEANS
.add("com.hector.batch.diffusion.ftr.FDPFtrRequestBodyDto");
IGNORED_BEANS
.add("com.hector.batch.diffusion.sisif.SisifDiffOfEnvoyeUsineRequestBodyDto");
IGNORED_BEANS
.add("com.hector.batch.luoreactualisee.PostEusiKeyDto");
IGNORED_BEANS
.add("com.hector.batch.replanification.officialisation.IodaDto");
IGNORED_BEANS.add("com.hector.rest.PEDBaseRepresentation");
IGNORED_BEANS
.add("com.hector.rest.chart.XDateYIntSizeValuesRepresentation");
IGNORED_BEANS
.add("com.hector.rest.chart.XDateYIntValuesRepresentation");
IGNORED_BEANS
.add("com.hector.rest.chart.XDateYIntZIntValuesRepresentation");
IGNORED_BEANS
.add("com.hector.rest.pfab.PossibiliteFabricationRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.AttributionCessionModeRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.AttributionCessionSemaineRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.AvanceRetardVolumeCafRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.CommandesEspaceAttenteRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.PopulationSFPRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.ResultatRessourceRepresentation");
IGNORED_BEANS
.add("com.hector.rest.replanification.SuiviQualitatifReplanifRepresentation");
IGNORED_BEANS
.add("com.hector.rest.ressource.AleaRessourceRepresentation");
IGNORED_BEANS.add("com.hector.rest.slot.SlotVoRepresentation");
IGNORED_BEANS.add("com.hector.rest.slot.TimeLineRepresentation");
}
@Test
public void testPEDAppDtos() {
new PEDBeansAutomatedTest().test("com.hector.app", TypeTest.Dto);
}
@Test
public void testPEDBatchDtos() {
new PEDBeansAutomatedTest().test("com.hector.batch", TypeTest.Dto);
}
@Test
public void testPEDDomainEntities() {
new PEDBeansAutomatedTest().test("com.hector.domain",
TypeTest.Entities);
}
@Test
public void testPEDDomainVos() {
new PEDBeansAutomatedTest().test("com.hector.domain", TypeTest.Vo);
}
@Test
public void testPEDWebRepresentations() {
new PEDBeansAutomatedTest().test("com.hector.rest",
TypeTest.Representations);
}
/**
* Méthode principale de test. <br>
* 1. Collecte les entités. <br>
* 2. Teste les entités. <br>
* 3. Affiche les résultats. <br>
*
* @param domainPkg
* package contenant les entités à tester. Seul un niveau
* d'arborescence est supporté pour l'instant (domain/container,
* domain/magasin....)
*/
public void test(String domainPkg, TypeTest typeTest) {
List <Class <?>> entitiesToTest = collectAllEntities(domainPkg, typeTest);
TestResult allTestsResults = new TestResult();
for (Class <?> entity : entitiesToTest) {
if (IGNORED_BEANS.contains(entity.getName())) {
LOGGER.debug("Bean {} ignored", entity.getName());
allTestsResults.addIgnoredBean();
} else {
allTestsResults.merge(testEntity(entity));
}
}
LOGGER.info("{} beans of type {} have been tested in {}",
entitiesToTest.size(), typeTest, domainPkg);
LOGGER.info(" --> {} beans ignored",
allTestsResults.getNbBeansIgnored());
LOGGER.info(" --> {} fields tested OK",
allTestsResults.getNbTestedSucceeded());
LOGGER.info(" --> {} fields tested KO",
allTestsResults.getNbTestedFailed());
LOGGER.info(
" --> {} fields could not be tested due to primitive/implementation issues",
allTestsResults.getNbNotTestedDueToPrimitiveOrImp());
LOGGER.info(" --> {} fields could not be tested due to other issues",
allTestsResults.getNbNotTestedOther());
LOGGER.info(" --> {} fields ignored", allTestsResults.getNbIgnored());
Assert.assertEquals(0, allTestsResults.getNbTestedFailed());
Assert.assertEquals(0,
allTestsResults.getNbNotTestedDueToPrimitiveOrImp());
Assert.assertEquals(0, allTestsResults.getNbNotTestedOther());
}
/**
* try to test the getter / setter of the entity. Return true if it was
* tested
*
* @param entityClass
* entity to test
* @return number of tested getter/setter pairs
*/
private TestResult testEntity(Class <?> entityClass) {
LOGGER.debug("Trying to test {}", entityClass.getName());
List <String> ignoredFields = (IGNORED_FIELDS.containsKey(entityClass
.getName())) ? IGNORED_FIELDS.get(entityClass.getName())
: new ArrayList <String>();
TestResult testResultClass = new TestResult();
try {
Object entityInstance = tryToInstantiateClass(entityClass);
if (entityInstance == null) {
LOGGER.warn("Bean {} could not be instantiated.",
entityClass.getName());
testResultClass.merge(TestResultType.NOT_TESTED_DUE_TO_OTHER
.getTestResult());
} else {
for (Field field : entityClass.getDeclaredFields()) {
if (ignoredFields.contains(field.getName())) {
LOGGER.debug("Field {} of {} ignored.",
field.getName(), entityClass.getName());
testResultClass.merge(TestResultType.IGNORED
.getTestResult());
} else {
Method getter = this.findGetter(entityClass, field);
Method setter = this.findSetter(entityClass, field);
if (getter != null && setter != null) {
LOGGER.debug(
"Getter and setter found for field {}.",
field.getName());
TestResultType oneTest = setThenGet(entityInstance,
field, setter, getter);
testResultClass.merge(oneTest.getTestResult());
}
}
}
}
} catch (SecurityException e) {
LOGGER.warn(e.getMessage(), e);
} catch (IllegalArgumentException e) {
LOGGER.warn(e.getMessage(), e);
}
return testResultClass;
}
/**
* Set puis get un objet. Vérifie que l'objet setté est bien l'objet getté.
*
* @param entityInstance
* instance de l'entité testée
* @param field
* attribut de l'instance
* @param setter
* setter de l'attribut
* @param getter
* getter de l'attribut
* @return résultat du test
*/
private TestResultType setThenGet(Object entityInstance, Field field,
Method setter, Method getter) {
TestResultType result = TestResultType.NOT_TESTED_DUE_TO_OTHER;
try {
Class <?> type = field.getType();
Object objectToSet = createInstanceOfType(type);
if (objectToSet == null) {
LOGGER.warn("Class {} : could not create {}", entityInstance
.getClass().getName(), field.getName());
result = TestResultType.NOT_TESTED_DUE_TO_PRIMITIVE_OR_IMPLEMENTATION;
} else {
// on fait un set puis un get. On vérifie ensuite que l'objet
// qu'on a set est bien celui qu'on get.
setter.invoke(entityInstance, objectToSet);
Object resultOfGet = getter.invoke(entityInstance);
// String msg = "Class " + entityInstance.getClass().getName() +
// ": one of " + getter.getName() + "()/"
// +
// setter.getName()
// + "() is wrong.\n" +
// "The getter do not return what the setter set.";
// if (!type.isPrimitive()) {
// Assert.assertTrue(msg, resultOfGet == objectToSet);
// } else {
// Assert.assertEquals(msg, objectToSet, resultOfGet);
// }
if ((!type.isPrimitive() && resultOfGet != objectToSet)
|| (type.isPrimitive() && !resultOfGet
.equals(objectToSet))) {
LOGGER.warn("Class " + entityInstance.getClass().getName()
+ ": one of " + getter.getName() + "()/"
+ setter.getName() + "() is wrong.\n"
+ "The getter do not return what the setter set.");
// AssertEquals(monObjetGet, monObjetSet);
result = TestResultType.TESTED_AND_FAILED;
} else {
LOGGER.debug("!!! successfully tested {}", field.getName());
result = TestResultType.TESTED_AND_SUCCEEDED;
}
}
} catch (IllegalAccessException e) {
LOGGER.warn(e.getMessage(), e);
} catch (IllegalArgumentException e) {
LOGGER.warn(e.getMessage(), e);
} catch (InvocationTargetException e) {
LOGGER.warn(e.getMessage(), e);
} catch (SecurityException e) {
LOGGER.warn(e.getMessage(), e);
}
return result;
}
/**
* Crée une instance de l'objet de type type ou fournis un objet par défaut
* si c'est une primitive, une enum, etc...
*
* @param type
* le type
* @return une instance/une primitive/une enum
*/
protected Object createInstanceOfType(Class <?> type) {
Object objectToSet = null;
try {
// si c'est une enum, on retourne la 1ere constante de l'enum
if (type.isEnum()) {
List <?> enumConstants = Arrays.asList(type.getEnumConstants());
objectToSet = enumConstants.get(0);
}
// si c'est une primitive ou une boxed primitive, on utilise un
// objet par défaut
else if (type.isPrimitive() || typeIsBoxedPrimitive(type)) {
objectToSet = PRIMITIVES_TO_USE.get(type);
if (objectToSet == null) {
LOGGER.warn("No primitive to use for {}", type);
}
}
// si c'est une interface, on utilise une implémentation par défaut
else if (type.isInterface()) {
Class <?> typeImp = IMPLEMENTATIONS_TO_USE.get(type);
if (typeImp == null) {
LOGGER.warn("No implementation defined for {}", type);
}
objectToSet = typeImp.newInstance();
}
// sinon, on invoke le constructeur par défaut
else {
objectToSet = tryToInstantiateClass(type);
}
// pire cas de figure : whitebox
if (objectToSet == null) {
objectToSet = Whitebox.newInstance(type);
}
} catch (IllegalArgumentException e) {
LOGGER.warn(e.getMessage(), e);
} catch (InstantiationException e) {
LOGGER.warn(e.getMessage(), e);
} catch (IllegalAccessException e) {
LOGGER.warn(e.getMessage(), e);
}
return objectToSet;
}
private Object tryToInstantiateClass(Class <?> type) {
Object instance = null;
Constructor <?>[] constructors = type.getDeclaredConstructors();
// Test every constructor
for (Constructor <?> constructor : constructors) {
constructor.setAccessible(true);
Class <?>[] paramTypes = constructor.getParameterTypes();
try {
// Constructor w/ params
List <Object> params = new ArrayList <Object>();
for (Class <?> paramType : paramTypes) {
params.add(paramType.getDeclaredConstructor().newInstance());
}
instance = constructor.newInstance(params.toArray());
} catch (NoSuchMethodException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException | SecurityException e) {
LOGGER.debug("Could not instantiate {} with {} ",
type.getName(), constructor.getName());
}
}
return instance;
}
/**
* Type est un type primitif
*
* @param type
* le type
* @return Type est un type primitif
*/
private boolean typeIsBoxedPrimitive(Class <?> type) {
return BOXED_PRIMITIVES.contains(type);
}
/**
* Trouve le setter pour un attribut d'une entitée
*
* @param entity
* la classe de l'entité
* @param field
* l'attribut
* @return le setter ou null si non trouvé
*/
private Method findSetter(Class <?> entity, Field field) {
for (String setterPrefix : POSSIBLE_SETTER_PREFIXES) {
try {
String setterToTry = setterPrefix
+ capitalizeFirstLetter(field.getName());
LOGGER.debug("Trying setter {}", setterToTry);
Method setter = entity.getMethod(setterToTry, field.getType());
return setter;
} catch (SecurityException e) {
LOGGER.warn("Security exception for class {}", entity.getName());
} catch (NoSuchMethodException e) {
LOGGER.debug("No setter found");
}
}
return null;
}
/**
* Renvoie la string avec la première lettre en majuscule
*
* @param str
* la string
* @return string avec la première lettre en majuscule
*/
private String capitalizeFirstLetter(String str) {
if (str.length() == 0) {
return str;
} else if (str.length() == 1) {
return String.valueOf(Character.toUpperCase(str.charAt(0)));
}
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}
/**
* Trouve le getter pour un attribut d'une entitée
*
* @param entity
* la classe de l'entité
* @param field
* l'attribut
* @return le getter ou null si non trouvé
*/
private Method findGetter(Class <?> entity, Field field) {
for (String getterPrefix : POSSIBLE_GETTER_PREFIXES) {
try {
String getterToTry = getterPrefix
+ capitalizeFirstLetter(field.getName());
LOGGER.debug("Trying getter {}", getterToTry);
Method getter = entity.getMethod(getterToTry);
return getter;
} catch (SecurityException e) {
LOGGER.warn("Security exception for class {}", entity.getName());
} catch (NoSuchMethodException e) {
LOGGER.debug("No setter found");
}
}
return null;
}
/**
* Collecte toutes les entités du package
*
* @param pkg
* le pkg de domain
* @return les entités collectées
*/
private List <Class <?>> collectAllEntities(String pkg, TypeTest typeTest) {
List <Class <?>> entitiesToTest = new ArrayList <Class <?>>();
try {
List <Class <?>> classes = getClasses(pkg);
for (Class <?> potentialEntity : classes) {
LOGGER.debug("Potential entity : " + potentialEntity.getName());
if (TypeTest.Entities.equals(typeTest)
&& potentialEntity.getAnnotation(Entity.class) != null) {
LOGGER.debug(" ->>>> this is an Entity");
entitiesToTest.add(potentialEntity);
} else if (TypeTest.Representations.equals(typeTest)
&& potentialEntity.getSimpleName().endsWith(
"Representation")) {
LOGGER.debug(" ->>>> this is a Representation");
entitiesToTest.add(potentialEntity);
} else if (TypeTest.Dto.equals(typeTest)
&& potentialEntity.getSimpleName().endsWith("Dto")) {
LOGGER.debug(" ->>>> this is a Dto");
entitiesToTest.add(potentialEntity);
} else if (TypeTest.Vo.equals(typeTest)
&& potentialEntity.getAnnotation(Embeddable.class) != null) {
LOGGER.debug(" ->>>> this is a Vo");
entitiesToTest.add(potentialEntity);
}
}
} catch (ClassNotFoundException | IOException e) {
Assert.fail("Unable to scan package " + pkg);
LOGGER.error("Unable to scan package.", e);
}
return entitiesToTest;
}
private static List <Class <?>> getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
Assert.assertNotNull(classLoader);
String path = packageName.replace('.', '/');
Enumeration <URL> resources = classLoader.getResources(path);
List <File> dirs = new ArrayList <File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList <Class <?>> classes = new ArrayList <Class <?>>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes;
}
/**
* Recursive method used to find all classes in a given directory and
* subdirs.
*
* @param directory
* The base directory
* @param packageName
* The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private static List <Class <?>> findClasses(File directory, String packageName)
throws ClassNotFoundException {
List <Class <?>> classes = new ArrayList <Class <?>>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
Assert.assertFalse(file.getName().contains("."));
classes.addAll(findClasses(file,
packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
try {
classes.add(Class.forName(packageName
+ '.'
+ file.getName().substring(0,
file.getName().length() - 6)));
} catch (Throwable e) {
LOGGER.warn("Could not initialize {}", file.getName());
}
}
}
return classes;
}
}
No hay comentarios:
Publicar un comentario