Il ne suffit pas de tester que nos fonctions réussissent
lorsqu’on leur passe des entrées correctes, nous devons aussi tester
qu’elles échouent lorsque les entrées sont incorrectes. Et pas
seulement qu’elles échouent, qu’elles échouent de la manière
prévue.
toRoman doit échouer s’il lui est passé
un entier hors de l’intervalle 1 à
3999.
toRoman doit échouer s’il lui est passé
une valeur non-entière.
En Python, les fonctions indiquent l’échec en déclenchant des exceptions et le module unittest
fournit des méthodes pour tester
si une fonction déclenche une exception en particulier lorsqu’on lui
donne une entrée incorrecte.
Exemple 13.3. Test des entrées incorrectes pour toRoman
class ToRomanBadInput(unittest.TestCase):
def testTooLarge(self):
"""toRoman should fail with large input"""
self.assertRaises(roman.OutOfRangeError, roman.toRoman, 4000) def testZero(self):
"""toRoman should fail with 0 input"""
self.assertRaises(roman.OutOfRangeError, roman.toRoman, 0) def testNegative(self):
"""toRoman should fail with negative input"""
self.assertRaises(roman.OutOfRangeError, roman.toRoman, -1)
def testNonInteger(self):
"""toRoman should fail with non-integer input"""
self.assertRaises(roman.NotIntegerError, roman.toRoman, 0.5)
La classe TestCase de unittest fournit la méthode
assertRaises, qui prend les arguments
suivants : l’exception attendue, la fonction testée et les
arguments à passer à cette fonction. (Si la fonction testée prend
plus d’un argument, passez-les tous à
assertRaises, dans l’ordre, qui les passera à
la fonction.) Faites bien attention à ce que nous faisons ici : au
lieu d’appeler la fonction toRoman
directement et de vérifier manuellement qu’elle déclenche une
exception particulière (en l’entourant d’un bloc
try...except),
assertRaises encapsule tout ça pour nous.
Tout ce que nous faisons est de lui donner l’exception
(roman.OutOfRangeError), la fonction
(toRoman) et les arguments de
toRoman (4000) et
assertRaises s’occupe d’appeler
toRoman et de vérifier qu’elle décleche
l’exception roman.OutOfRangeError. (Est-ce
que j’ai dit récemment comme il est pratique que tout en
Python est un objet, y compris
les fonctions et les exceptions ?)
En plus de tester les nombres trop grand, nous devons tester
les nombres trop petits. Rappelez-vous, les chiffres romains ne
peuvent exprimer 0 ou des valeurs négatives,
donc nous avons un cas de test pour chacun
(testZero et
testNegative). Dans
testZero, nous testons que
toRoman déclenche une exception
roman.OutOfRangeError lorsqu’on l’appelle
avec 0, si l’exception
roman.OutOfRangeError n’est
pas déclenchée (soit parce qu’une valeur est
retournée, soit parce qu’une autre exception est déclenchée), le
test est considéré comme ayant échoué.
La spécification
n°3 précise que toRoman ne peut
accepter de non-entier, nous testons donc ici le déclenchement
d’une exception roman.NotIntegerError
lorsque toRoman est appelée avec un nombre
décimal (0.5). Si toRoman
ne déclenche pas l’exception
roman.NotIntegerError, les test est
considéré comme ayant échoué.
Les deux spécifications
suivantes sont similaires aux trois premières, excepté le fait qu’elles
s’appliquent à fromRoman au lieu de
fromRoman :
fromRoman doit prendre un nombre en chiffres romains
valide et retourner la valeur qu’il représente.
fromRoman doit échouer s’il lui est passé
un nombre romain invalide.
La spécification n°4 est prise en charge de la même manière que la
spécification
n°1, en parcourant un échantillon de valeurs connues et en les
testant une à une. La spécification n°5 est prise en charge de la même
manière que les spécifications n°2 et 3, en testant une série d’entrées
incorrectes et en s’assurant que fromRoman
déclenche l’exception appropriée.
Exemple 13.4. Test des entrées incorrectes pour fromRoman
class FromRomanBadInput(unittest.TestCase):
def testTooManyRepeatedNumerals(self):
"""fromRoman should fail with too many repeated numerals"""for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s) def testRepeatedPairs(self):
"""fromRoman should fail with repeated pairs of numerals"""for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s)
def testMalformedAntecedent(self):
"""fromRoman should fail with malformed antecedents"""for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s)
Il n’y a pas grand chose de nouveau à dire, c’est la même
méthode que celle que nous avons employé pour tester les entrées
incorrectes pour toRoman. Je mentionne juste
qu’il y a une nouvelle exception :
roman.InvalidRomanNumeralError. Cela fait
un total de trois exceptions personnalisées à définir dans
roman.py (avec
roman.OutOfRangeError et
roman.NotIntegerError). Nous verrons
comment définir ces exceptions quand nous commenceront vraiment
l’écriture de roman.py au chapitre suivant.