Nacházíte se zde: Domů Ponořme se do Pythonu 3

Úroveň obtížnosti: ♦♦♦♦♢

Refaktorizace

After one has played a vast quantity of notes and more notes, it is simplicity that emerges as the crowning reward of art.
(Poté, co jste zahráli ohromné množství not a ještě více not, se jako vrcholná odměna umění objeví jednoduchost.)
Frédéric Chopin

 

Ponořme se

K chybám dochází, ať se vám to líbí nebo ne. Chyby se objeví navzdory vašemu nejlepšímu úsilí o vytvoření všezahrnujících testů jednotek (unit test). Co vlastně myslím slovem „chyba“? Chybou rozumím testovací případ (test case), který jste ještě nenapsali.

>>> import roman7
>>> roman7.from_roman('') 
0
  1. Tohle je chyba. Prázdný řetězec by měl vyvolat výjimku InvalidRomanNumeralError stejně jako jiné posloupnosti znaků, které nevyjadřují platné římské číslo.

Jakmile chybu umíte navodit, měli byste napsat testovací případ (test case) ještě dříve, než ji opravíte. Tím chybu popíšete.

class FromRomanBadInput(unittest.TestCase):
    .
    .
    .
    def testBlank(self):
        '''from_roman should fail with blank string'''
        self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, '') 
  1. Je to docela jednoduché. Voláme funkci from_roman() s prázdným řetězcem a ujišťujeme se, že vyvolává výjimku InvalidRomanNumeralError. Nalezení chyby je obtížnou částí úkolu. Pokud už o ní víme, představuje její otestování snadnou část úkolu.

Protože náš kód obsahuje chybu a protože už máme k dispozici testovací případ, který ji popisuje, dojde k jeho selhání:

you@localhost:~/diveintopython3/examples$ python3 romantest8.py -v
from_roman should fail with blank string ... FAIL
from_roman should fail with malformed antecedents ... ok
from_roman should fail with repeated pairs of numerals ... ok
from_roman should fail with too many repeated numerals ... ok
from_roman should give known result with known input ... ok
to_roman should give known result with known input ... ok
from_roman(to_roman(n))==n for all n ... ok
to_roman should fail with negative input ... ok
to_roman should fail with non-integer input ... ok
to_roman should fail with large input ... ok
to_roman should fail with 0 input ... ok

======================================================================
FAIL: from_roman should fail with blank string
----------------------------------------------------------------------
Traceback (most recent call last):
  File "romantest8.py", line 117, in test_blank
    self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, '')
AssertionError: InvalidRomanNumeralError not raised by from_roman

----------------------------------------------------------------------
Ran 11 tests in 0.171s

FAILED (failures=1)

Teď už chybu můžeme opravit.

def from_roman(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: {}'.format(s))  

    result = 0
    index = 0
    for numeral, integer in romanNumeralMap:
        while s[index:index+len(numeral)] == numeral:
            result += integer
            index += len(numeral)
    return result
  1. Musíme přidat jen dva řádky kódu: explicitní kontrolu na prázdný řetězec a příkaz raise.
  2. Myslím, že o tomhle jsem se v této knize zatím ještě nezmínil. Nechť to slouží jako závěrečná lekce z formátování řetězců. Počínaje verzí Python 3.1 můžete při specifikaci formátu vynechat čísla pozičních indexů. To znamená, že místo specifikátoru {0}, kterým se odkazujeme na první parametr metody format(), můžeme jednoduše použít {} a Python doplní správný poziční index za nás. Funguje to pro libovolný počet argumentů. První {} se chápe jako {0}, druhý výskyt {} znamená {1} a tak dále.
you@localhost:~/diveintopython3/examples$ python3 romantest8.py -v
from_roman should fail with blank string ... ok  
from_roman should fail with malformed antecedents ... ok
from_roman should fail with repeated pairs of numerals ... ok
from_roman should fail with too many repeated numerals ... ok
from_roman should give known result with known input ... ok
to_roman should give known result with known input ... ok
from_roman(to_roman(n))==n for all n ... ok
to_roman should fail with negative input ... ok
to_roman should fail with non-integer input ... ok
to_roman should fail with large input ... ok
to_roman should fail with 0 input ... ok

----------------------------------------------------------------------
Ran 11 tests in 0.156s

OK  
  1. Testovací případ pro prázdný řetězec prošel, takže chyba je opravena.
  2. Všechny ostatní testovací případy prošly také. To znamená, že jsme opravou chyby nic jiného nepokazili. Přestaňte psát kód.

Tento přístup k programování opravu chyb nijak neusnadňuje. Jednoduché chyby (jako je tato) vyžadují jednodušší testovací případy, složité chyby povedou k složitým testovacím případům. V prostředí soustředěném kolem testů se může zdát, že oprava chyby trvá déle. Musíme chybu přesně popsat v kódu (tj. musíme napsat testovací případ) a teprve potom ji opravit. Pokud testovací případ hned neprojde, musíme zjistit, zda jsme udělali chybu v opravě, nebo zda je chyba v kódu testovacího případu. Ale z dlouhodobého hlediska se střídavá tvorba testovacího a testovaného kódu vyplatí, protože se tím zvyšuje pravděpodobnost správné opravy chyb napoprvé. S vaším novým testem se také snadno opakovaně spouštějí všechny testy. Proto je málo pravděpodobné, že opravou nového kódu pokazíte původní kód. Dnešní test jednotky (unit test) je zítřejším regresním testem.

Zvládání měnících se požadavků

Navzdory vašemu nejlepšímu úsilí o připíchnutí zákazníka k zemi, poté co z něj při bolestivé proceduře zahrnující hrůzné odpornosti (jako jsou nůžky a horký vosk) vytáhnete přesné požadavky... ty požadavky se změní. Většina zákazníků neví, co chce, dokud to neuvidí. A dokonce když už to vidí, nejsou dost dobří na to, aby vyjádřili, co chtějí, tak přesně, aby to k něčemu bylo. A dokonce i když se vyjádří přesně, v příští verzi toho stejně budou chtít víc. Takže v souvislosti s měnícími se požadavky buďte připraveni na úpravy svých testovacích případů (test case).

Dejme tomu, že bychom například chtěli rozšířit rozsah funkce pro převod římských čísel. V římských číslech se žádný znak nemůže opakovat víc než třikrát. Ale Římané byli ochotni připustit výjimku z tohoto pravidla a reprezentovat hodnotu 4000 uvedením čtyř M za sebou. Pokud takovou změnu provedeme, budeme schopni rozšířit rozsah převáděných čísel z 1..3999 na 1..4999. Ale nejdříve provedeme úpravy testovacích případů.

[stáhnout roman8.py]

class KnownValues(unittest.TestCase):
    known_values = ( (1, 'I'),
                      .
                      .
                      .
                     (3999, 'MMMCMXCIX'),
                     (4000, 'MMMM'),                                      
                     (4500, 'MMMMD'),
                     (4888, 'MMMMDCCCLXXXVIII'),
                     (4999, 'MMMMCMXCIX') )

class ToRomanBadInput(unittest.TestCase):
    def test_too_large(self):
        '''to_roman should fail with large input'''
        self.assertRaises(roman8.OutOfRangeError, roman8.to_roman, 5000)  

    .
    .
    .

class FromRomanBadInput(unittest.TestCase):
    def test_too_many_repeated_numerals(self):
        '''from_roman should fail with too many repeated numerals'''
        for s in ('MMMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):     
            self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, s)

    .
    .
    .

class RoundtripCheck(unittest.TestCase):
    def test_roundtrip(self):
        '''from_roman(to_roman(n))==n for all n'''
        for integer in range(1, 5000):                                    
            numeral = roman8.to_roman(integer)
            result = roman8.from_roman(numeral)
            self.assertEqual(integer, result)
  1. Stávající známé hodnoty se nemění (pořád jde o rozumné testovací hodnoty), ale potřebujeme přidat pár dalších v rozsahu od 4000. Přidali jsme 4000 (nejkratší), 4500 (druhé nejkratší), 4888 (nejdelší) a 4999 (největší).
  2. Změnila se definice „velké vstupní hodnoty“. U tohoto testu se při volání to_roman() s hodnotou 4000 očekávala chyba. Teď se ale rozsah 4000–4999 považuje za správné hodnoty, proto musíme hranici zvýšit na 5000.
  3. Změnila se také definice „příliš mnoho opakujících se znaků“. U tohoto testu se při volání tfrom_roman() se vstupem 'MMMM' očekávala chyba. Teď je MMMM považováno za platné římské číslo. Testovací hodnotu musíme zvětšit na 'MMMMM'.
  4. Test funkčnosti procházel v cyklu každým číslem z intervalu 13999. Rozsah se teď rozšířil, takže cyklus for musíme upravit, aby se dostal až k 4999.

Teď máme testovací případy upraveny ve shodě s novými požadavky, ale kód zatím ne. Takže se dá čekat, že některé z testů selžou.

you@localhost:~/diveintopython3/examples$ python3 romantest9.py -v
from_roman should fail with blank string ... ok
from_roman should fail with malformed antecedents ... ok
from_roman should fail with non-string input ... ok
from_roman should fail with repeated pairs of numerals ... ok
from_roman should fail with too many repeated numerals ... ok
from_roman should give known result with known input ... ERROR          
to_roman should give known result with known input ... ERROR            
from_roman(to_roman(n))==n for all n ... ERROR                          
to_roman should fail with negative input ... ok
to_roman should fail with non-integer input ... ok
to_roman should fail with large input ... ok
to_roman should fail with 0 input ... ok

======================================================================
ERROR: from_roman should give known result with known input
----------------------------------------------------------------------
Traceback (most recent call last):
  File "romantest9.py", line 82, in test_from_roman_known_values
    result = roman9.from_roman(numeral)
  File "C:\home\diveintopython3\examples\roman9.py", line 60, in from_roman
    raise InvalidRomanNumeralError('Invalid Roman numeral: {0}'.format(s))
roman9.InvalidRomanNumeralError: Invalid Roman numeral: MMMM

======================================================================
ERROR: to_roman should give known result with known input
----------------------------------------------------------------------
Traceback (most recent call last):
  File "romantest9.py", line 76, in test_to_roman_known_values
    result = roman9.to_roman(integer)
  File "C:\home\diveintopython3\examples\roman9.py", line 42, in to_roman
    raise OutOfRangeError('number out of range (must be 0..3999)')
roman9.OutOfRangeError: number out of range (must be 0..3999)

======================================================================
ERROR: from_roman(to_roman(n))==n for all n
----------------------------------------------------------------------
Traceback (most recent call last):
  File "romantest9.py", line 131, in testSanity
    numeral = roman9.to_roman(integer)
  File "C:\home\diveintopython3\examples\roman9.py", line 42, in to_roman
    raise OutOfRangeError('number out of range (must be 0..3999)')
roman9.OutOfRangeError: number out of range (must be 0..3999)

----------------------------------------------------------------------
Ran 12 tests in 0.171s

FAILED (errors=3)
  1. Test známých hodnot pro from_roman() selže v okamžiku, kdy se dostane k hodnotě 'MMMM'. Funkce from_roman() si totiž pořád myslí, že jde o neplatné římské číslo.
  2. Test známých hodnot pro to_roman() selže v okamžiku, kdy se narazí na hodnotu 4000, protože to_roman() ji stále považuje za hodnotu mimo rozsah.
  3. Kruhový test selže rovněž u hodnoty 4000, protože to_roman() ji považuje za hodnotu mimo rozsah.

Máme tedy testovací případy, které selhávají v důsledku nových požadavků, a můžeme uvažovat o opravení kódu do odpovídajícího stavu. (Když s psaním testů jednotek (unit test) začínáte, můžete mít divný pocit, že testovaný kód nikdy „nepředbíhá“ testovací případy. Dokud je pozadu, máme pořád nějakou práci před sebou. Jakmile doběhne testovací případy, přestaneme jej upravovat. Jakmile si na to jednou zvyknete, budete se divit, jak jste vůbec dříve mohli programovat bez testů.)

[stáhnout roman9.py]

roman_numeral_pattern = re.compile('''
    ^                   # začátek řetězce
    M{0,4}              # tisíce - 0 až 4 M       
    (CM|CD|D?C{0,3})    # stovky - 900 (CM), 400 (CD), 0-300 (0 až 3 C),
                        #        nebo 500-800 (D následované 0 až 3 C)
    (XC|XL|L?X{0,3})    # desítky - 90 (XC), 40 (XL), 0-30 (0 až 3 X),
                        #        nebo 50-80 (L následované 0 až 3 X)
    (IX|IV|V?I{0,3})    # jednotky - 9 (IX), 4 (IV), 0-3 (0 až 3 I),
                        #        nebo 5-8 (V následované 0 až 3 I)
    $                   # konec řetězce
    ''', re.VERBOSE)

def to_roman(n):
    '''convert integer to Roman numeral'''
    if not isinstance(n, int):
        raise NotIntegerError('non-integers can not be converted')
    if not (0 < n < 5000):                        
        raise OutOfRangeError('number out of range (must be 1..4999)')

    result = ''
    for numeral, integer in roman_numeral_map:
        while n >= integer:
            result += numeral
            n -= integer
    return result

def from_roman(s):
    .
    .
    .
  1. Funkci from_roman() nemusíme vůbec upravovat. Změna se týká jen vzorku roman_numeral_pattern. Při podrobnějším pohledu zjistíte, že jsem v první části regulárního výrazu změnil maximální počet nepovinných znaků M z 3 na 4. Tím povolíme čísla odpovídající hodnotě až 4999 místo původní 3999. Samotná funkce from_roman() je zcela obecná. Zkrátka jen hledá opakující se znaky římského čísla a sčítá odpovídající hodnoty. Nestará se o to, kolikrát se opakují. Dříve nezvládala 'MMMM' pouze z toho důvodu, že jsme ji explicitně zastavili na základě porovnání s regulárním výrazem.
  2. Funkce to_roman() si vyžádá jen jednu malou změnu v místě kontroly rozsahu. Kde jsme dříve testovali 0 < n < 4000, budeme teď kontrolovat 0 < n < 5000. A hlášení o chybě vyvolávané příkazem raise změníme tak, aby odpovídalo novému povolenému rozsahu (1..4999 místo 1..3999). Zbytek funkce nemusíme měnit. Nové případy zvládá. (Vesele přidává 'M' pro každou nalezenou tisícovku. Když dostane 4000 vychrlí 'MMMM'. Dříve tento případ nezvládala jen proto, že jsme ji explicitně zastavili při kontrole rozsahu.)

Možná pochybujete o tom, že by tyhle dvě malé změny vyřešily vše, co potřebujeme. Nemusíte mi to věřit. Zkontrolujte si to sami.

you@localhost:~/diveintopython3/examples$ python3 romantest9.py -v
from_roman should fail with blank string ... ok
from_roman should fail with malformed antecedents ... ok
from_roman should fail with non-string input ... ok
from_roman should fail with repeated pairs of numerals ... ok
from_roman should fail with too many repeated numerals ... ok
from_roman should give known result with known input ... ok
to_roman should give known result with known input ... ok
from_roman(to_roman(n))==n for all n ... ok
to_roman should fail with negative input ... ok
to_roman should fail with non-integer input ... ok
to_roman should fail with large input ... ok
to_roman should fail with 0 input ... ok

----------------------------------------------------------------------
Ran 12 tests in 0.203s

OK  
  1. Všechny testovací případy prošly. Přestaňte psát kód.

Při používání obsáhlých testů jednotek nemusíte spoléhat na programátora, který říká: „Věř mi.“

Refaktorizace

Na komplexním používání testů jednotek (unit testing) není nejlepší to, jak se cítíte, když všechny testovací případy nakonec projdou, dokonce ani to, jak se cítíte, když vás někdo nařkne, že jste mu pokazili jeho kód, a vy ve skutečnosti můžete dokázat, že tomu tak není. Na testech jednotek je nejlepší věcí to, že vám dává volnost nemilosrdně refaktorizovat.

Refaktorizace je činností, kdy vezmete fungující kód a uděláte z něj ještě lepší. „Lepší“ obvykle znamená „rychlejší“, ale může to taky znamenat „používající méně paměti“ nebo „používající menší diskový prostor“ nebo je prostě „elegantnější“. Refaktorizace je z hlediska dlouhodobého zdraví každého programu důležitá, ať už to znamená cokoliv pro vás, pro váš projekt nebo pro vaše okolí.

V případě našeho kódu bude „lepší“ znamenat jak „rychlejší“, tak „snadněji udržovatelný“. Konkrétně funkce from_roman() je pomalejší a složitější, než by se mi líbilo. Je to dáno oním velkým, hnusným regulárním výrazem, který se používá pro ověřování, zda jde o římské číslo. Teď si možná pomyslíte: „No jo. Ten regulární výraz sice je velký a střapatý, ale jak jinak by se dalo ověřit, zda je libovolný řetězec platným římským číslem?“

Odpověď zní: Těch čísel je jen 5000. Proč bychom pro ně prostě nemohli vytvořit vyhledávací tabulku? Ta myšlenka se vám bude líbit ještě víc, když zjistíte, že vůbec nebudeme potřebovat regulární výrazy. Při budování vyhledávací tabulky pro převod čísel na římská čísla můžeme současně vytvářet opačnou vyhledávací tabulku pro konverzi římských čísel na celá čísla. Při testu, zda je libovolný řetězec platným římským číslem, budeme mít k dispozici všechna platná římská čísla. „Ověření platnosti“ se redukuje na jedno vyhledání ve slovníku.

A ze všeho nejlepší je, že už máme k dispozici úplnou sadu testů jednotek (unit test). V modulu můžeme vyměnit klidně polovinu kódu, ale testy jednotek zůstanou stejné. To znamená, že můžete dokázat — sami sobě a ostatním —, že nový kód funguje stejně dobře jako ten původní.

[stáhnout roman10.py]

class OutOfRangeError(ValueError): pass
class NotIntegerError(ValueError): pass
class InvalidRomanNumeralError(ValueError): pass

roman_numeral_map = (('M',  1000),
                     ('CM', 900),
                     ('D',  500),
                     ('CD', 400),
                     ('C',  100),
                     ('XC', 90),
                     ('L',  50),
                     ('XL', 40),
                     ('X',  10),
                     ('IX', 9),
                     ('V',  5),
                     ('IV', 4),
                     ('I',  1))

to_roman_table = [ None ]
from_roman_table = {}

def to_roman(n):
    '''convert integer to Roman numeral'''
    if not (0 < n < 5000):
        raise OutOfRangeError('number out of range (must be 1..4999)')
    if int(n) != n:
        raise NotIntegerError('non-integers can not be converted')
    return to_roman_table[n]

def from_roman(s):
    '''convert Roman numeral to integer'''
    if not isinstance(s, str):
        raise InvalidRomanNumeralError('Input must be a string')
    if not s:
        raise InvalidRomanNumeralError('Input can not be blank')
    if s not in from_roman_table:
        raise InvalidRomanNumeralError('Invalid Roman numeral: {0}'.format(s))
    return from_roman_table[s]

def build_lookup_tables():
    def to_roman(n):
        result = ''
        for numeral, integer in roman_numeral_map:
            if n >= integer:
                result = numeral
                n -= integer
                break
        if n > 0:
            result += to_roman_table[n]
        return result

    for integer in range(1, 5000):
        roman_numeral = to_roman(integer)
        to_roman_table.append(roman_numeral)
        from_roman_table[roman_numeral] = integer

build_lookup_tables()

Rozdělme si to na stravitelné kousky. Prokazatelně nejdůležitějším řádkem je ten poslední:

build_lookup_tables()

Jistě si všimnete, že jde o volání funkce. Ale není tu žádný obalující příkaz if. Tady nejde o blok uvnitř if __name__ == '__main__'. Funkce se zavolá v okamžiku importu modulu. (Zde je důležité vědět, že se moduly importují jen jednou a poté se pamatují ve vyrovnávací paměti (cache). Pokud importujeme už jednou importovaný modul, nic se neděje. Takže uvedený kód bude zavolán jen při prvním importu tohoto modulu.)

Co vlastně funkce build_lookup_tables() dělá? To jsem rád, že se ptáte.

to_roman_table = [ None ]
from_roman_table = {}
.
.
.
def build_lookup_tables():
    def to_roman(n):                                
        result = ''
        for numeral, integer in roman_numeral_map:
            if n >= integer:
                result = numeral
                n -= integer
                break
        if n > 0:
            result += to_roman_table[n]
        return result

    for integer in range(1, 5000):
        roman_numeral = to_roman(integer)          
        to_roman_table.append(roman_numeral)       
        from_roman_table[roman_numeral] = integer
  1. Tohle je takový chytrý programátorský obrat... možná až příliš chytrý. Funkce to_roman() je definována výše. Vyhledává hodnoty ve vyhledávací tabulce a vrací je. Ale funkce build_lookup_tables() si pro realizaci převodu vytváří svou vlastní definici funkce to_roman() (stejnou, jaká se používala v předchozích případech, než jsme přidali vyhledávací tabulku). Uvnitř funkce build_lookup_tables() se bude volat ta redefinovaná verze funkce to_roman(). Jakmile funkce build_lookup_tables() skončí, redefinovaná verze zmizí. Její definice je platná jen lokálně, uvnitř funkce build_lookup_tables().
  2. Na tomto řádku kódu se volá redefinovaná funkce to_roman(), která ve skutečnosti vytváří římské číslo.
  3. Jakmile máme k dispozici výsledek (redefinované funkce to_roman()), přidáme číslo a jemu odpovídající římské číslo do obou vyhledávacích tabulek.

Jakmile jsou vyhledávací tabulky naplněny, je zbývající kód jednoduchý a rychlý.

def to_roman(n):
    '''convert integer to Roman numeral'''
    if not (0 < n < 5000):
        raise OutOfRangeError('number out of range (must be 1..4999)')
    if int(n) != n:
        raise NotIntegerError('non-integers can not be converted')
    return to_roman_table[n]                                            

def from_roman(s):
    '''convert Roman numeral to integer'''
    if not isinstance(s, str):
        raise InvalidRomanNumeralError('Input must be a string')
    if not s:
        raise InvalidRomanNumeralError('Input can not be blank')
    if s not in from_roman_table:
        raise InvalidRomanNumeralError('Invalid Roman numeral: {0}'.format(s))
    return from_roman_table[s]                                          
  1. Funkce to_roman() provede stejné kontroly hraničních případů (jako dříve) a potom jednoduše najde odpovídající hodnotu ve vyhledávací tabulce a vrátí ji.
  2. Také funkce from_roman() je redukována na kontroly a jeden řádek kódu. Už žádné regulární výrazy. Už žádné cykly. Převod na a z římského čísla se složitostí O(1) — tj. v konstantním čase.

Ale funguje to? Proč se ptáte? Jasně že funguje. A můžu to dokázat.

you@localhost:~/diveintopython3/examples$ python3 romantest10.py -v
from_roman should fail with blank string ... ok
from_roman should fail with malformed antecedents ... ok
from_roman should fail with non-string input ... ok
from_roman should fail with repeated pairs of numerals ... ok
from_roman should fail with too many repeated numerals ... ok
from_roman should give known result with known input ... ok
to_roman should give known result with known input ... ok
from_roman(to_roman(n))==n for all n ... ok
to_roman should fail with negative input ... ok
to_roman should fail with non-integer input ... ok
to_roman should fail with large input ... ok
to_roman should fail with 0 input ... ok

----------------------------------------------------------------------
Ran 12 tests in 0.031s                                                  

OK
  1. Tedy, ne že byste se ptali, ale ono je to taky rychlé! Skoro 10krát rychlejší. Není to, samozřejmě, úplně férové srovnání, protože u této verze trvá déle import (budují se vyhledávací tabulky). Ale protože se import dělá jen jednou, rozpustí se nákladnost při startu mezi volání funkcí to_roman() a from_roman(). A protože se při testech provádí několik tisíc volání funkcí (jen samotný kruhový test jich provede 10 000), úspory se rychle nasčítají!

A jak zní ponaučení?

Shrnutí

Unit testing (testování jednotek) představuje mocný koncept, který při správné implementaci vede u dlouhodobých projektů jak k redukci nákladů na údržbu, tak ke zvýšení pružnosti. Současně si ale musíme uvědomit, že testování jednotek není všelék. Napsat dobré testové případy není jednoduchá věc a udržet je v aktuálním stavu vyžaduje disciplínu (zvlášť když zákazníci vřískají, aby byly opraveny kritické chyby). Unit testing není náhradou ostatních forem testování, zahrnujících testování funkčnosti celého systému, integrační testování (tj. test spolupráce jednotek) a uživatelské akceptační testy. Testy jednotek jsou ale přesto rozumné, fungují, a když už je jednou uvidíte v činnosti, budete se divit, jak jste se bez nich mohli obejít.

V pár posledních kapitolách jsme se šířeji zabývali základy, z nichž mnohé dokonce nejsou specifické jen pro Python. Rámce pro testování jednotek (unit testing frameworks) jsou dostupné pro mnoho jazyků a všechny vyžadují, abyste porozuměli týmž konceptům:

© 2001–11 Mark Pilgrim