Si une classe contient un tas de méthodes statiques, afin de s'assurer que personne n'initialise par erreur une instance de cette classe, j'ai créé un constructeur privé:
private Utils() {
}
Maintenant .. comment cela pourrait-il être testé, étant donné que le constructeur ne peut pas être vu? Ce test peut-il être couvert du tout?
En utilisant la réflexion, vous pouvez invoquer un constructeur privé:
Constructor<Util> c = Utils.class.getDeclaredConstructor();
c.setAccessible(true);
Utils u = c.newInstance(); // Hello sailor
Cependant, vous pouvez rendre cela impossible:
private Utils() {
throw new UnsupportedOperationException();
}
En lançant une exception dans le constructeur, vous empêchez toutes les tentatives.
Je ferais aussi la classe final
, juste "parce que":
public final class Utils {
private Utils() {
throw new UnsupportedOperationException();
}
}
Testez l'intention du code .. toujours :)
Par exemple: si le but du constructeur d'être privé ne doit pas être vu, alors ce que vous devez tester est ce fait et rien d'autre.
Utilisez la réflexion API pour interroger les constructeurs et valider qu'ils ont l'ensemble d'attributs privés.
Je ferais quelque chose comme ça:
@Test()
public void testPrivateConstructors() {
final Constructor<?>[] constructors = Utils.class.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
}
}
Si vous souhaitez avoir un test approprié pour la construction de l'objet, vous devez tester l'API publique qui vous permet d'obtenir l'objet construit. C'est la raison pour laquelle ladite API devrait exister: pour construire correctement les objets, vous devez donc la tester :).
@Test
public//
void privateConstructorTest() throws Exception {
final Constructor<?>[] constructors = Utils.class.getDeclaredConstructors();
// check that all constructors are 'private':
for (final Constructor<?> constructor : constructors) {
Assert.assertTrue(Modifier.isPrivate(constructor.getModifiers()));
}
// call the private constructor:
constructors[0].setAccessible(true);
constructors[0].newInstance((Object[]) null);
}
pour vous assurer que personne n'initialise par erreur une instance de cette classe
Habituellement, ce que je fais, c'est de changer la méthode/constructeur de privé à la visibilité du package par défaut. Et j'utilise le même package pour ma classe de test, donc à partir du test la méthode/constructeur est accessible, même si ce n'est pas de l'extérieur.
Pour appliquer la stratégie afin de ne pas instancier la classe, vous pouvez:
ou appliquez les deux 1 + 2, vous pouvez toujours sous-classer et exécuter le constructeur si votre test partage le même package que la classe cible. Cela devrait être assez "à l'abri des erreurs"; les codeurs malveillants trouveront toujours une solution :)
Si vous avez un constructeur privé, il est appelé à partir d'une méthode pas si privée de votre code. Vous testez donc cette méthode et votre constructeur est couvert. Il n'y a aucune vertu religieuse à avoir un test par méthode. Vous recherchez une fonction ou mieux encore un niveau de couverture de branche, et vous pouvez l'obtenir simplement en exerçant le constructeur via le chemin de code qui l'utilise.
Si ce chemin de code est compliqué et difficile à tester, vous devrez peut-être le refactoriser.
Si vous ajoutez une exception dans le constructeur telle que:
private Utils() {
throw new UnsupportedOperationException();
}
L'invocation de constructor.newInstance()
dans la classe de test lancera un InvocationTargetException
au lieu de votre UnsupportedOperationException
, mais l'exception souhaitée sera contenue dans celle levée.
Si vous voulez affirmer la levée de l'exception votre, vous pouvez lever la cible de l'exception d'invocation, une fois que l'exception d'invocation a été interceptée.
Par exemple, en utilisant jUnit 4, vous pouvez faire ceci:
@Test(expected = UnsupportedOperationException.class)
public void utilityClassTest() throws NoSuchMethodException, IllegalAccessException, InstantiationException {
final Constructor<Utils> constructor = Utils.class.getDeclaredConstructor();
constructor.setAccessible(true);
try {
constructor.newInstance();
} catch (InvocationTargetException e) {
throw (UnsupportedOperationException) e.getTargetException();
}
}