J'ai une interface List
dont les implémentations incluent Singly Linked List, Doubly, Circular, etc. Les tests unitaires que j'ai écrits pour Singly devraient être utiles à Doubly, Circular et toute autre nouvelle implémentation de l'interface. Ainsi, au lieu de répéter les tests unitaires pour chaque implémentation, JUnit propose-t-il quelque chose de intégré qui me permettrait d’avoir un test JUnit et de le lancer contre différentes implémentations?
En utilisant des tests paramétrés JUnit, je peux fournir différentes implémentations telles que Singly, doublement, circulaire, etc., mais pour chaque implémentation, le même objet est utilisé pour exécuter tous les tests de la classe.
Avec JUnit 4.0+, vous pouvez utiliser tests paramétrés :
@RunWith(value = Parameterized.class)
à votre appareil de testpublic static
renvoyant Collection
, annotez-la avec @Parameters
et insérez SinglyLinkedList.class
, DoublyLinkedList.class
, CircularList.class
, etc. dans cette collection.Class
: public MyListTest(Class cl)
et stockez la Class
dans une variable d'instance listClass
setUp
ou @Before
, utilisez List testList = (List)listClass.newInstance();
Une fois la configuration ci-dessus en place, le programme paramétré créera une nouvelle instance de votre appareil de test MyListTest
pour chaque sous-classe fournie dans la méthode @Parameters
, vous permettant ainsi d'appliquer la même logique de test pour chaque sous-classe à tester.
J'éviterais probablement les tests paramétrés de JUnit (qui à mon humble avis sont assez mal implémentés) et crée simplement une classe de test abstraite List
qui pourrait être héritée par les implémentations de tests:
public abstract class ListTestBase<T extends List> {
private T instance;
protected abstract T createInstance();
@Before
public void setUp() {
instance = createInstance();
}
@Test
public void testOneThing(){ /* ... */ }
@Test
public void testAnotherThing(){ /* ... */ }
}
Les différentes implémentations obtiennent ensuite leurs propres classes concrètes:
class SinglyLinkedListTest extends ListTestBase<SinglyLinkedList> {
@Override
protected SinglyLinkedList createInstance(){
return new SinglyLinkedList();
}
}
class DoublyLinkedListTest extends ListTestBase<DoublyLinkedList> {
@Override
protected DoublyLinkedList createInstance(){
return new DoublyLinkedList();
}
}
La bonne chose à propos de le faire de cette façon (au lieu de créer une classe de test qui teste toutes les implémentations) est que, si vous souhaitez tester certains cas spécifiques, vous pouvez simplement ajouter plusieurs tests à la sous-classe de test spécifique. .
Je sais que cela est vieux, mais j’ai appris à le faire dans une variante légèrement différente qui fonctionne bien, dans laquelle vous pouvez appliquer le @Parameter
à un membre de champ pour injecter les valeurs.
C'est juste un peu plus propre à mon avis.
@RunWith(Parameterized.class)
public class MyTest{
private ThingToTest subject;
@Parameter
public Class clazz;
@Parameters(name = "{index}: Impl Class: {0}")
public static Collection classes(){
List<Object[]> implementations = new ArrayList<>();
implementations.add(new Object[]{ImplementationOne.class});
implementations.add(new Object[]{ImplementationTwo.class});
return implementations;
}
@Before
public void setUp() throws Exception {
subject = (ThingToTest) clazz.getConstructor().newInstance();
}
Basé sur les réponses de @dasblinkenlight et this , j'ai proposé une implémentation que je souhaiterais partager avec mon cas d'utilisation.
J'utilise ServiceProviderPattern ( API de différence et SPI ) pour les classes qui implémentent l'interface IImporterService
. Si une nouvelle implémentation de l'interface est développée, seul un fichier de configuration dans META-INF/services/ doit être modifié pour enregistrer l'implémentation.
Le fichier dans META-INF/services/ est nommé d'après le nom de classe complet de l'interface de service (IImporterService
), par exemple.
de.myapp.importer.IImporterService
Ce fichier contient une liste des casses qui implémentent IImporterService
, par exemple.
de.myapp.importer.impl.OfficeOpenXMLImporter
La classe d'usine ImporterFactory
fournit aux clients des implémentations concrètes de l'interface.
La ImporterFactory
renvoie une liste de toutes les implémentations de l'interface, enregistrées via le ServiceProviderPattern . La méthode setUp()
garantit qu'une nouvelle instance est utilisée pour chaque scénario de test.
@RunWith(Parameterized.class)
public class IImporterServiceTest {
public IImporterService service;
public IImporterServiceTest(IImporterService service) {
this.service = service;
}
@Parameters
public static List<IImporterService> instancesToTest() {
return ImporterFactory.INSTANCE.getImplementations();
}
@Before
public void setUp() throws Exception {
this.service = this.service.getClass().newInstance();
}
@Test
public void testRead() {
}
}
La méthode ImporterFactory.INSTANCE.getImplementations()
se présente comme suit:
public List<IImporterService> getImplementations() {
return (List<IImporterService>) GenericServiceLoader.INSTANCE.locateAll(IImporterService.class);
}
Vous pouvez réellement créer une méthode d'assistance dans votre classe de test qui configure votre test List
en tant qu'instance d'une de vos implémentations dépendant d'un argument . En combinaison avec this , vous devriez pouvoir obtenir le comportement tu veux.
En développant la première réponse, les aspects Parameter de JUnit4 fonctionnent très bien. Voici le code que j'ai utilisé dans un projet testant des filtres. La classe est créée à l'aide d'une fonction fabrique (getPluginIO
) et la fonction getPluginsNamed
obtient toutes les classes PluginInfo avec le nom utilisant SezPoz et des annotations afin de permettre la détection automatique des nouvelles classes.
@RunWith(value=Parameterized.class)
public class FilterTests {
@Parameters
public static Collection<PluginInfo[]> getPlugins() {
List<PluginInfo> possibleClasses=PluginManager.getPluginsNamed("Filter");
return wrapCollection(possibleClasses);
}
final protected PluginInfo pluginId;
final IOPlugin CFilter;
public FilterTests(final PluginInfo pluginToUse) {
System.out.println("Using Plugin:"+pluginToUse);
pluginId=pluginToUse; // save plugin settings
CFilter=PluginManager.getPluginIO(pluginId); // create an instance using the factory
}
//.... the tests to run
Notez qu'il est important (personnellement, je ne sais pas pourquoi cela fonctionne de cette façon) de faire en sorte que la collection soit un ensemble de tableaux du paramètre réel transmis au constructeur, dans ce cas une classe appelée PluginInfo. La fonction statique wrapCollection effectue cette tâche.
/**
* Wrap a collection into a collection of arrays which is useful for parameterization in junit testing
* @param inCollection input collection
* @return wrapped collection
*/
public static <T> Collection<T[]> wrapCollection(Collection<T> inCollection) {
final List<T[]> out=new ArrayList<T[]>();
for(T curObj : inCollection) {
T[] arr = (T[])new Object[1];
arr[0]=curObj;
out.add(arr);
}
return out;
}