Les énumérations sont simplement des types finis, avec des noms personnalisés (espérons-le significatifs). Une énumération peut n'avoir qu'une seule valeur, comme celle void
qui ne contient que null
(certaines langues appellent cela unit
, et utilisent le nom void
d'une énumération sans éléments!). Il peut avoir deux valeurs, comme celle bool
qui a false
et true
. Il peut avoir trois, comme colourChannel
avec red
, green
et blue
. Etc.
Si deux énumérations ont le même nombre de valeurs, elles sont alors "isomorphes"; c'est-à-dire que si nous supprimons systématiquement tous les noms, nous pouvons en utiliser un à la place d'un autre et notre programme ne se comportera pas différemment. En particulier, nos tests ne se comporteront pas différemment!
Par exemple, result
contenir win
/ lose
/ draw
est isomorphe à ce qui précède colourChannel
, car nous pouvons remplacer par exemple colourChannel
par result
, red
avec win
, green
avec lose
et blue
avec draw
, et tant que nous le faisons partout (producteurs et consommateurs, analyseurs et sérialiseurs, entrées de base de données, fichiers journaux, etc. ), il n'y aura alors aucun changement dans notre programme. Tous les " colourChannel
tests" que nous avons écrits passeront toujours, même s'il n'y en a colourChannel
plus!
De plus, si une énumération contient plusieurs valeurs, nous pouvons toujours réorganiser ces valeurs pour obtenir une nouvelle énumération avec le même nombre de valeurs. Étant donné que le nombre de valeurs n'a pas changé, le nouvel arrangement est isomorphe à l'ancien, et donc nous pourrions changer tous les noms et nos tests passeraient toujours (notez que nous ne pouvons pas simplement changer la définition; nous devons désactiver également tous les sites d'utilisation).
Cela signifie que, en ce qui concerne la machine, les énumérations sont des "noms distinctifs" et rien d'autre . La seule chose que nous pouvons faire avec une énumération est de déterminer si deux valeurs sont identiques (par exemple red
/ red
) ou différentes (par exemple red
/ blue
). Voilà donc la seule chose qu'un «test unitaire» peut faire, par exemple
( red == red ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
( red != green) || throw TestFailure;
( red != blue ) || throw TestFailure;
...
Comme le dit @ jesm00, un tel test vérifie l' implémentation du langage plutôt que votre programme. Ces tests ne sont jamais une bonne idée: même si vous ne faites pas confiance à l'implémentation du langage, vous devez le tester de l'extérieur , car il ne peut pas faire confiance pour exécuter les tests correctement!
Voilà donc la théorie; qu'en est-il de la pratique? Le principal problème avec cette caractérisation des énumérations est que les programmes du `` monde réel '' sont rarement autonomes: nous avons des versions héritées, des déploiements à distance / intégrés, des données historiques, des sauvegardes, des bases de données en direct, etc. donc nous ne pouvons jamais vraiment `` basculer '' toutes les occurrences d'un nom sans manquer certaines utilisations.
Pourtant, de telles choses ne relèvent pas de la «responsabilité» de l'énumération elle-même: la modification d'une énumération peut interrompre la communication avec un système distant, mais inversement, nous pouvons résoudre un tel problème en modifiant une énumération!
Dans de tels scénarios, l'ENUM est un hareng saur: si un système dont il a besoin d'être cette façon, et un autre , il doit être que ça? Ça ne peut pas être les deux, peu importe le nombre de tests que nous écrivons! Le vrai coupable ici est l'interface d'entrée / sortie, qui devrait produire / consommer des formats bien définis plutôt que "quel que soit l'entier choisi par l'interprète". La vraie solution est donc de tester les interfaces d'E / S : avec des tests unitaires pour vérifier qu'il analyse / imprime le format attendu, et avec des tests d'intégration pour vérifier que le format est bien accepté par l'autre côté.
Nous pouvons encore nous demander si l'énumération est «suffisamment exercée», mais dans ce cas, l'énumération est à nouveau un hareng rouge. Ce qui nous préoccupe réellement, c'est la suite de tests elle-même . Nous pouvons gagner en confiance ici de deux manières:
- La couverture du code peut nous dire si la variété des valeurs d'énumération provenant de la suite de tests est suffisante pour déclencher les différentes branches du code. Sinon, nous pouvons ajouter des tests qui déclenchent les branches découvertes, ou générer une plus grande variété d'énumérations dans les tests existants.
- La vérification des propriétés peut nous dire si la variété des branches dans le code est suffisante pour gérer les possibilités d'exécution. Par exemple, si le code ne gère que
red
, et que nous testons uniquement avec red
, alors nous avons une couverture à 100%. Un vérificateur de propriétés va (essayer de) générer des contre-exemples à nos assertions, comme générer les valeurs green
et que blue
nous avons oublié de tester.
- Les tests de mutation peuvent nous dire si nos assertions vérifient réellement l'énumération, plutôt que de simplement suivre les branches et ignorer leurs différences.