Se moquant de Java enum pour ajouter une valeur de test en cas d'échec
J'ai un enum basculer plus ou moins comme ceci:
public static enum MyEnum {A, B}
public int foo(MyEnum value) {
switch(value) {
case(A): return calculateSomething();
case(B): return calculateSomethingElse();
}
throw new IllegalArgumentException("Do not know how to handle " + value);
}
et j'aimerais avoir toutes les lignes couvertes par les tests, mais comme le code est prévu pour faire face à toutes les possibilités, je ne peux pas fournir une valeur sans son correspondant en cas de déclaration dans le commutateur.
L'extension de l'enum pour ajouter une valeur supplémentaire n'est pas possible, et juste se moquer de la méthode equals pour revenir false
ne fonctionne pas, soit parce que le bytecode généré utilise un saut de la table de derrière les rideaux pour aller à la appropriée des cas... Donc j'ai pensé que peut-être un peu de magie noire pourrait être réalisé avec des PowerMock ou quelque chose.
Merci!
modifier:
Que je possède de cette énumération, j'ai pensé que je pourrais juste ajouter une méthode à les valeurs, et ainsi d'éviter le commutateur problème complètement; mais je pars de la question qu'il est encore intéressant.
- le code qui s'exécute l'instruction switch() java throws java.lang.Erreur arrayindexoutofbounds ... j'ai ce même Problème. Exécutez votre test avec une nouvelle énumération que le premier dans votre Classe de Test. J'ai créé un bug avec ce Problème: code.google.com/p/powermock/issues/detail?id=440
- Il fonctionne mieux lorsque j'utilise @PrepareForTest(MyEnum.class) au niveau de la Méthode.
- Un IlegalArgument qui ne peut jamais être jeté, car il est évident propriétés de l'enum, mais vous bastardise votre code de test qu'il va gérer l'impossible? Si vous voulez vraiment fetishise votre ligne civerage métrique, pourquoi ne pas simplement supprimer la ligne qui ne peut jamais être exécuté?
- 2 raisons: tout d'abord, quelqu'un d'autre pourrait créer une nouvelle valeur de l'enum et oublier d'ajouter un nouveau cas pour le commutateur; deuxièmement, le code ne compile pas sans
throw
oureturn
après le changement. - Après réflexion, je pense juste laisser pas été testé. Il n'y a pas illégal valeur d'enum pour le déclenchement de l'Exception et il est douloureux de se moquer. Je pense que le jet est bon, c'est l'avenir, vraiment dur à tester. Pas la peine de l'effort de test, à mon humble avis.
Vous devez vous connecter pour publier un commentaire.
Voici un exemple complet.
Le code est presque comme votre origine (juste simplifié meilleur test de validation):
Et voici le test de l'unité avec le code complet de la couverture, le test fonctionne avec Powermock (1.4.10), Mockito (1.8.5) et JUnit (4.8.2):
Résultat:
Whitebox.setInternalState(C, "ordinal", 2);
il est possible de passerfoo
au lieu de2
. Et initialiserfoo
commeint foo = MyEnum.values().length;
en tant que première chaîne (ci-dessusMyEnum C = PowerMockito.mock(MyEnum.class);
)Plutôt que d'utiliser certains radicaux de manipulation de bytecode pour permettre à un test de frapper à la dernière ligne dans
foo
, je voudrais l'enlever et s'appuient sur l'analyse statique de code à la place. Par exemple, IntelliJ IDEA a la "Enumswitch
déclaration qui manque case" code de l'inspection, ce qui permettrait de produire un avertissement pour lefoo
méthode si elle manquait uncase
.throw
déclaration devient redondante et peut être retiré, car le manque d'case
dans leswitch
serait détecté par l'IDE/build.Que vous avez indiqué dans votre montage, vous pouvez ajouter le functionaliy dans le enum lui-même. Toutefois, cela pourrait ne pas être la meilleure option, car elle peut constituer une infraction à la "Une Responsabilité" principe. Une autre façon d'y parvenir est de créer un mappage statique qui contient les valeurs de l'enum comme la clé et la fonctionnalité de la valeur. De cette façon, vous pouvez facilement tester si, pour toute valeur d'enum la validité de comportement en boucle sur toutes les valeurs. Il peut être un peu tiré par les cheveux sur cet exemple, mais c'est une technique que j'utilise souvent à la carte id de ressource à enum valeurs.
jMock (au moins à partir de la version 2.5.1 que j'utilise) peut le faire hors de la boîte. Vous aurez besoin de mettre votre Moquerie à l'utilisation ClassImposterizer.
Juste de créer une fausse valeur d'enum ne sera pas suffisant, vous avez également besoin de manipuler un tableau d'entiers qui est créé par le compilateur.
En fait de créer une fausse valeur d'enum, vous n'avez même pas besoin de se moquant de cadre. Vous pouvez simplement utiliser Objenesis pour créer une nouvelle instance de la classe enum (oui, cela fonctionne) puis utilisez plain old Java réflexion pour définir les champs privés
name
etordinal
et vous avez déjà votre nouvelle enum instance.À l'aide de Spock cadre des tests, cela ressemblerait à quelque chose comme:
Si vous aussi vous voulez le
MyEnum.values()
méthode pour renvoyer la nouvelle enum, vous pouvez maintenant utiliser JMockit de se moquer de l'values()
appel commeou vous pouvez à nouveau utiliser le bon vieux réflexion pour manipuler les
$VALUES
domaine comme:Aussi longtemps que vous n'avez pas affaire à un
switch
expression, mais avec quelquesif
s ou similaire, soit juste ou de la première partie de la première et de la deuxième partie peut être suffisant pour vous.Si vous faites affaire avec un
switch
expression, d'e. g. vouloir une couverture de 100% pour ledefault
affaire, qui lève une exception dans le cas où l'enum est agrandi comme dans votre exemple, les choses deviennent un peu plus compliqué et en même temps un peu plus facile.Un peu plus compliqué car vous avez besoin de faire un peu de sérieux de la réflexion afin de manipuler un terrain synthétique que le compilateur génère dans un synthétique anonyme innner classe que le compilateur génère, de sorte qu'il n'est vraiment pas évident ce que vous faites et vous êtes lié à la mise en œuvre effective du compilateur, ce qui pourrait casser à tout moment, dans toute version de Java, ou même si vous utilisez des compilateurs différents pour la même version de Java. Il est déjà différente entre Java 6 et Java 8.
Un peu plus facile, parce que vous pouvez oublier les deux premières parties de cette réponse, parce que vous n'avez pas besoin de créer un nouveau enum exemple à tous, vous avez juste besoin de manipuler un
int[]
, que vous avez besoin de manipuler de toute façon pour faire le test que vous souhaitez.J'ai récemment trouvé un très bon article au sujet de cette à https://www.javaspecialists.eu/archive/Issue161.html.
La plupart des informations y est toujours valable, sauf que maintenant l'intérieur de la classe contenant le commutateur de la carte n'est plus un nom interne de la classe, mais la classe anonyme, de sorte que vous ne pouvez pas utiliser
getDeclaredClasses
, mais plus besoin d'utiliser une approche différente indiqué ci-dessous.Fondamentalement résumé, interrupteur sur le bytecode à niveau ne fonctionne pas avec les énumérations, mais seulement avec des entiers. Donc, ce que le compilateur n'est, elle crée un anonyme intérieur de la classe (précédemment nommée intérieure que par l'écriture d'article, c'est Java 6 vs Java 8) qui est titulaire d'un static final
int[]
champ appelé$SwitchMap$net$kautler$MyEnum
qui est rempli avec des nombres entiers 1, 2, 3, ... sur les indices deMyEnum#ordinal()
valeurs.Cela signifie que lorsque le code est livré pour le changement réel, il ne
Si maintenant
myEnumVariable
aurait la valeurNON_EXISTENT
créé dans la première étape ci-dessus, vous obtenez uneArrayIndexOutOfBoundsException
si vous définissezordinal
à une valeur supérieure à la matrice de l'généré par le compilateur, ou vous obtenez l'un de l'autre interrupteur valeurs si ce n'est, dans les deux cas, ce ne serait pas aider à tester le voulaitdefault
cas.Vous pouvez maintenant obtenir ce
int[]
champ et le fixer en place pour contenir un mappage pour l'orinal de votreNON_EXISTENT
enum instance. Mais comme je l'ai dit plus tôt, exactement pour ce cas d'utilisation, les tests de ladefault
cas, vous n'avez pas besoin de la première de deux étapes à tous. Au lieu de cela, vous pouvez simplement donner un enum cas pour le code testé et tout simplement de manipuler la cartographieint[]
, de sorte que ledefault
cas est déclenchée.Donc tout ce qui est nécessaire pour ce test est en fait, encore une fois écrit dans Spock (Groovy) du code, mais vous pouvez facilement l'adapter à Java trop:
Dans ce cas, vous n'avez pas besoin de se moquant de cadre à tous. En fait, il ne serait pas vous aider en tout cas, pas de moqueries cadre, je suis conscient de vous permet de simuler un tableau. Vous pouvez utiliser JMockit ou tout se moquant de cadre pour se moquer de la valeur de retour de
ordinal()
, mais ce serait encore simplement changer l'interrupteur de la succursale ou d'une AIOOBE.Ce que ce code j'ai juste indiqué n'est:
ClassNotFoundException
est jeté parClass.forName
, le test échoue, ce qui est prévu, parce que cela signifie que vous avez compilé le code avec un compilateur qui suit une stratégie différente ou un modèle de nommage, si vous avez besoin d'ajouter un peu plus d'intelligence pour couvrir les différentes compilateur stratégies pour la commutation sur les valeurs de l'enum. Parce que si la classe avec le terrain est trouvé, lebreak
quitte la boucle for et le test peut continuer. Toute cette stratégie dépend bien sûr de anonyme classes étant numérotés à partir de 1 et sans lacunes, mais j'espère que c'est plutôt une hypothèse sûre. Si vous faites affaire avec un compilateur où ce n'est pas le cas, l'algorithme de recherche doit être adapté en conséquence.Integer.MAX_VALUE
qui normalement devrait déclencher ladefault
cas aussi longtemps que vous n'avez pas un enum avec de 2 147 483 647 valeursbreak
finally
bloc si vous n'êtes pas à l'aide de Spock, dans uncleanup
bloc si vous utilisez Spock) pour s'assurer que cela n'affecte pas les autres tests sur le même classe, l'original de changer la carte est remise dans le commutateur de la carte de champTout d'abord Mockito peut créer des maquettes de données qui peut être de type entier long, etc
Il ne peut pas créer de droit enum comme enum a nombre spécifique de l'ordinal de nom
valeur etc donc, si j'ai un enum
j'ai donc total 5 ordinale dans enum HttpMethod mais mockito ne le savent pas .Mockito crée se moquer de données et de ses null tout le temps et vous finirez en passant une valeur null .
Voici donc la solution proposée qui vous rendre aléatoire l'ordinal et d'obtenir un droit enum qui peut être adoptée pour d'autres test
De sortie :
Je pense que la façon la plus simple pour atteindre le IllegalArgumentException est de passer la valeur null à la méthode foo et vous pourrez lire "je Ne sais pas comment gérer la valeur null"
switch
ing sur unnull
valeur donne uneNullPointerException
et ne suivent pas ladefault
cas (dans l'exemple, il tombe à travers, d'après l'switch
déclaration.Je mettrais le cas par défaut avec l'un des enum cas: