Zanurkuj w Pythonie/Testowanie poprawnych przypadków

Tworzenie poszczególnych przypadków testowych należy do najbardziej podstawowych elementów testowania jednostkowego. Przypadek testowy stanowi odpowiedź na pewne pytanie dotyczące kodu, który jest testowany.

Przypadek testowy powinien:

  • ...działać bez konieczności wprowadzania danych przez człowieka. Testowanie jednostkowe powinno być zautomatyzowane.
  • ...samodzielnie stwierdzać, czy testowana funkcja działa poprawnie, czy nie, bez konieczności interpretacji wyników przez człowieka.
  • ...działać w izolacji, oddzielnie i niezależnie od innych przypadków testowych (nawet wówczas, gdy testują one te same funkcje). Każdy przypadek testowy powinien być "wyspą".

Zbudujmy więc pierwszy przypadek testowy, biorąc powyższe pod uwagę. Mamy następujące wymaganie:


  1. Funkcja toRoman powinna zwracać tekstową reprezentację w zapisie rzymskim wszystkich liczb całkowitych z przedziału od 1 do 3999.

Przykład 13.2. testToRomanKnownValues

class KnownValues(unittest.TestCase):                                   #(1)
    knownValues = ( (1, 'I'),
                    (2, 'II'),
                    (3, 'III'),
                    (4, 'IV'),
                    (5, 'V'),
                    (6, 'VI'),
                    (7, 'VII'),
                    (8, 'VIII'),
                    (9, 'IX'),
                    (10, 'X'),
                    (50, 'L'),
                    (100, 'C'),
                    (500, 'D'),
                    (1000, 'M'),
                    (31, 'XXXI'),
                    (148, 'CXLVIII'),
                    (294, 'CCXCIV'),
                    (312, 'CCCXII'),
                    (421, 'CDXXI'),
                    (528, 'DXXVIII'),
                    (621, 'DCXXI'),
                    (782, 'DCCLXXXII'),
                    (870, 'DCCCLXX'),
                    (941, 'CMXLI'),
                    (1043, 'MXLIII'),
                    (1110, 'MCX'),
                    (1226, 'MCCXXVI'),
                    (1301, 'MCCCI'),
                    (1485, 'MCDLXXXV'),
                    (1509, 'MDIX'),
                    (1607, 'MDCVII'),
                    (1754, 'MDCCLIV'),
                    (1832, 'MDCCCXXXII'),
                    (1993, 'MCMXCIII'),
                    (2074, 'MMLXXIV'),
                    (2152, 'MMCLII'),
                    (2212, 'MMCCXII'),
                    (2343, 'MMCCCXLIII'),
                    (2499, 'MMCDXCIX'),
                    (2574, 'MMDLXXIV'),
                    (2646, 'MMDCXLVI'),
                    (2723, 'MMDCCXXIII'),
                    (2892, 'MMDCCCXCII'),
                    (2975, 'MMCMLXXV'),
                    (3051, 'MMMLI'),
                    (3185, 'MMMCLXXXV'),
                    (3250, 'MMMCCL'),
                    (3313, 'MMMCCCXIII'),
                    (3408, 'MMMCDVIII'),
                    (3501, 'MMMDI'),
                    (3610, 'MMMDCX'),
                    (3743, 'MMMDCCXLIII'),
                    (3844, 'MMMDCCCXLIV'),
                    (3888, 'MMMDCCCLXXXVIII'),
                    (3940, 'MMMCMXL'),
                    (3999, 'MMMCMXCIX'))                                #(2)

    def testToRomanKnownValues(self):                                   #(3)
        """toRoman should give known result with known input"""
        for integer, numeral in self.knownValues:              
            result = roman.toRoman(integer)                             #(4) #(5)
            self.assertEqual(numeral, result)                           #(6)


  1. W celu utworzenia przypadku testowego tworzymy nową podklasę klasy TestCase z modułu unittest. Klasa TestCase udostępnia wiele użytecznych metod, które można użyć we własnym przypadku testowym celem przetestowania określonych warunków.
  2. Jest to lista par liczba całkowita/wartość w zapisie rzymskim, których poprawność sprawdziłem ręcznie. Zawiera ona dziesięć najniższych liczb, liczbę największą, każdą liczbę, która jest reprezentowana przy pomocy jednego znaku w zapisie rzymskim oraz pewne inne, losowo wybrane wartości. Celem przypadku testowego nie jest przetestowanie wszystkich mogących się pojawić danych wejściowych, lecz pewnej reprezentatywnej próbki.
  3. Każdy pojedynczy test posiada swoją metodę, która nie bierze żadnych parametrów oraz nie zwraca żadnej wartości. Jeśli metoda zakończy się normalnie bez rzucenia wyjątku, uznaje się wówczas, że taki test przeszedł; jeśli z metody zostanie rzucony wyjątek, wówczas uznaje się, że test nie przeszedł.
  4. W tym miejscu wołamy funkcję toRoman. (Rzeczywiście, funkcja ta nie została jeszcze napisana, ale kiedy już ją napiszemy, ta właśnie linijka spowoduje jej wywołanie). Zauważmy, że właśnie zdefiniowaliśmy API funkcji toRoman: pobiera ona argument typu int (liczbę, która ma zostać przekształcona na zapis rzymski) i zwraca wartość typu string (rzymską reprezentację wartości przekazanej w parametrze). Jeśli rzeczywiste API będzie inne, ten test zakończy się niepowodzeniem.
  5. Zauważmy również, że podczas wywoływania toRoman nie łapiemy żadnych wyjątków. Jest to celowe. Funkcja toRoman nie powinna zgłaszać wyjątków w sytuacji, gdy wywołujemy ją z prawidłowymi wartościami, a wszystkie wartości, z którymi ją wywołujemy, są poprawne. Jeśli toRoman rzuci wyjątek, test zakończy się niepowodzeniem.
  6. Jeśli założymy, że funkcja toRoman została poprawnie zdefiniowana i wywołana oraz poprawnie się zakończyła, zwracając pewną wartość, to ostatnią rzeczą, jaką musimy sprawdzić, jest to, czy zwrócona wartość jest poprawna. Tego rodzaju sprawdzenie jest bardzo powszechne, a w klasie TestCase istnieje metoda assertEqual, która może w tym pomóc: sprawdza ona, czy dwie wartości są sobie równe. Jeśli wartość zwrócona przez funkcję toRoman (result) nie jest równa znanej nam, spodziewanej wartości (numeral), assertEqual spowoduje rzucenie wyjątku, a test zakończy się niepowodzeniem. Jeśli te dwie wartości są równe, metoda ta nic nie robi. Jeśli każda wartość zwrócona przez toRoman pasuje do wartości, której się spodziewamy, to assertEqual nigdy nie rzuci wyjątku, a więc testToRomanKnownValues zakończy się normalnie, co oznacza, że funkcja toRoman przeszła ten test.