You are here: Partenza > Dive Into Python > Test delle unità di codice > Come gestire gli errori di programmazione | << >> | ||||
Dive Into PythonPython per programmatori esperti |
A dispetto dei nostri migliori sforzi per scrivere test completi per le unità di codice, capita di fare degli errori di programmazione, in gergo chiamati bachi (“bug”). Un baco corrisponde ad un test che non è stato ancora scritto.
>>> import roman5 >>> roman5.fromRoman("") 0
Ricordate nella sezione precedente quando continuavamo a dire che una stringa vuota corrispondeva con l'espressione regolare che usavamo per controllare un numero romano valido? Bene, capita che questo sia ancora valido per la versione finale dell'espressione regolare. Questo è un baco; noi vogliamo che una stringa vuota sollevi un'eccezione InvalidRomanNumeralError esattamente come ogni altra sequenza di caratteri che non rappresenta un numero romano valido. |
Dopo aver ricostruito il baco, e prima di porvi rimedio, è opportuno scrivere un test che fallisca a causa del baco, illustrandone così le caratteristiche.
class FromRomanBadInput(unittest.TestCase): # previous test cases omitted for clarity (they haven’t changed) def testBlank(self): """fromRoman should fail with blank string""" self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, "")
Dato che il nostro codice ha un baco, e noi abbiamo un test che verifica questo baco, il test fallirà.
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... FAIL fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ====================================================================== FAIL: fromRoman should fail with blank string ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage6\romantest61.py", line 137, in testBlank self.assertRaises(roman61.InvalidRomanNumeralError, roman61.fromRoman, "") File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ---------------------------------------------------------------------- Ran 13 tests in 2.864s FAILED (failures=1)
Ora possiamo risolvere il problema.
def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, 'Input can not be blank' if not re.search(romanNumeralPattern, s): raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with blank string ... ok fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------- Ran 13 tests in 2.834s OK
Scrivere codice in questo modo non serve a rendere più facile l'eliminazione dei bachi. Bachi semplici (come questo) richiedono test semplici; casi più complessi richiedono test più complessi. In un ambiente orientato ai test, può sembrare che ci voglia più tempo per eliminare un baco, giacché è necessario dettagliare in forma di codice esattamente qual'è il baco (per scrivere il test), e poi eliminare il problema. Quindi, se il test non passa subito con successo, occorre scoprire se ad essere sbagliata sia la correzione del problema o il test. Tuttavia, alla distanza, questo andare avanti e indietro tra il codice da verificare ed il codice di test ripaga, perché rende più probabile che un baco sia eliminato correttamente al primo colpo. Inoltre, dato che è possibile eseguire i vecchi test insieme ai i nuovi, è molto meno probabile che si danneggi il codice esistente quando si cerca di eliminare il baco. I test delle unità di codice di oggi saranno i test di regressione di domani.
<< roman.py, fase 5 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | |
Gestire il cambiamento di requisiti >> |