web-dev-qa-db-fra.com

Comment tester un constructeur privé dans une application Java?

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?

44
JAM

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();
    }
}
68
Bohemian

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 :).

19
Mihai Toader
@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);
}
6
MrSmith42

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:

  1. lancez UnsupportedOperationException ("n'instanciez pas cette classe!") à partir du constructeur vide par défaut.
  2. déclarer la classe abstraite: si elle ne contient que des méthodes statiques, vous pouvez appeler les méthodes statiques mais pas l'instancier, sauf si vous la sous-classe.

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 :)

5
Luigi R. Viggiano

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.

4
bmargulies

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();
    }
}
3
Juan