J’ai une List
interfaces dont les implémentations incluent Singly Linked List, Doubly, Circular etc. Les tests unitaires que j’ai écrits pour Singly devraient faire du bien pour la plupart de Doubly ainsi que pour Circular et toute autre nouvelle implémentation de l’interface. Donc, au lieu de répéter les tests unitaires pour chaque implémentation, JUnit offre-t-il quelque chose qui pourrait me permettre d’avoir un test JUnit et de l’exécuter avec différentes implémentations?
En utilisant les tests paramétrés JUnit, je peux fournir différentes implémentations comme Singly, Doubly, Circular etc., mais pour chaque implémentation, le même object est utilisé pour exécuter tous les tests de la classe.
Avec JUnit 4.0+, vous pouvez utiliser des tests paramétrés :
@RunWith(value = Parameterized.class)
à votre @RunWith(value = Parameterized.class)
test public static
renvoyant Collection
, @Parameters
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();
Avec la configuration ci-dessus en place, le runner paramétré créera une nouvelle instance de votre assembly de test MyListTest
pour chaque sous-classe que vous fournissez dans la méthode @Parameters
, vous permettant d’exercer la même logique de test pour chaque sous-classe à tester.
J’éviterais probablement les tests paramétrés de JUnit (dont l’implémentation est plutôt maladroite), et je ferais juste une classe de test de List
abstraite qui pourrait être héritée par les implémentations de tests:
public abstract class ListTestBase { 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 alors leurs propres classes concrètes:
class SinglyLinkedListTest extends ListTestBase { @Override protected SinglyLinkedList createInstance(){ return new SinglyLinkedList(); } } class DoublyLinkedListTest extends ListTestBase { @Override protected DoublyLinkedList createInstance(){ return new DoublyLinkedList(); } }
La bonne chose à faire de cette façon (au lieu de créer une seule classe de test qui teste toutes les implémentations) est que si vous souhaitez tester quelques cas particuliers avec une seule implémentation, vous pouvez simplement append d’autres tests à la sous-classe de test spécifique. .
Je sais que c’est ancien, mais j’ai appris à le faire dans une variante légèrement différente, qui fonctionne parfaitement, dans laquelle vous pouvez appliquer le @Parameter
à un membre du 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
Sur la base de la réponse de @dasblinkenlight et de cette réponse, je suis venu avec une implémentation pour mon cas d’utilisation que je voudrais partager.
J’utilise le 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 de 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.
ImporterFactory
renvoie une liste de toutes les implémentations de l’interface, enregistrées via ServiceProviderPattern . La méthode setUp()
garantit qu’une nouvelle instance est utilisée pour chaque setUp()
test.
@RunWith(Parameterized.class) public class IImporterServiceTest { public IImporterService service; public IImporterServiceTest(IImporterService service) { this.service = service; } @Parameters public static List 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()
ressemble à ceci:
public List getImplementations() { return (List ) GenericServiceLoader.INSTANCE.locateAll(IImporterService.class); }
Vous pourriez en fait créer une méthode d’assistance dans votre classe de test qui configure votre List
test pour qu’elle soit une instance de l’une de vos implémentations dépendant d’un argument. En combinaison avec cela, vous devriez être capable d’obtenir le comportement que vous souhaitez.
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 pour tester les filtres. La classe est créée en utilisant une fonction usine ( getPluginIO
) et la fonction getPluginsNamed
obtient toutes les classes PluginInfo avec le nom en utilisant SezPoz et des annotations pour permettre la détection automatique de nouvelles classes.
@RunWith(value=Parameterized.class) public class FilterTests { @Parameters public static Collection getPlugins() { List 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) d’avoir la collection en tant que collection de tableaux du paramètre réel fourni au constructeur, en l’occurrence 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 Collection wrapCollection(Collection inCollection) { final List out=new ArrayList(); for(T curObj : inCollection) { T[] arr = (T[])new Object[1]; arr[0]=curObj; out.add(arr); } return out; }