unittest Labs

Počas tohto lab-u si vyskúšame priebeh TDD - celý proces Red-Green-Refactor. V rámci lab-u budeme vytvárať testy pre objekt reprezentujúci bankový účet používateľa. Pomocou tejto triedy bude možné:

  • vytvoriť nový bankový účet, ktorého stav priamo po vytvorení bude 0
  • vložiť na existujúci bankový účet peniaze
  • vybrať z existujúceho bankového účtu peniaze
  • previesť peniaze z účtu na účet

Vždy však musí platiť podmienka, že výška bankového účtu musí byť vyššia ako 0. Pre jednoduchosť budú všetky hodnoty reprezentujúce množstvo peňazí na účte typu integer.

Tvorba jednoduchých testov (TestCase)

  1. Vytvorte prázdnu triedu BankAccountTest, ktorá bude reprezentovať TestCase pre testovanie implementácie triedy BankAccount

    from unittest import TestCase
    
    class BankAccountTest(TestCase):
       pass
    
  2. Navrhnite a vytvorte test, pomocou ktorého overíte, že po vytvorení nového účtu, bude jeho výška 0.

    def test_account_balance_is_zero_after_it_was_created(self):
      account = BankAccount()
      self.assertEquals(account.get_balance(), 0)
    
  3. Spustite test.

    Po spustení testu tento samozrejme zlyhá, nakoľko trieda BankAccount ako ani jej metóda get_balance() zatiaľ neexistujú. To je však úplne v poriadku, nakoľko sme stále vo fáze Red a vytvorili sme test, ktorý zlyhá. V nasledujúcej úlohe sa pokúsime test sprevádzkovať.

  4. Spojazdnite test.

    Aby sme test spojazdnili, musíme vytvoriť ako triedu BankAccount, tak aj jej metódu get_balance(), ktorá vráti aktuálny stav účtu. Keďže však programujeme voči testu, môže výsledná implementácia, ktorá testom prejde, vyzerať aj takto:

    class BankAccount(object):
    
       def get_balance(self):
          return 0;
    

    Po spustení testu bude tento tentokrát zelený.

  5. Navrhnite a vytvorte test, pomocou ktorého overíte, že ak na účet vložíte nejaký finančný obnos, tento sa na účte následne aj objaví.

    Pre vkladanie peňazí na účet budeme používať metódu deposit(). V rámci testu teda:

    1. vytvoríme nový účet (výška účtu bude po vytvorení 0)
    2. vložíme na účet 1000 eur
    3. otestujeme, či sa na účte týchto 1000 eur nachádza
    def test_account_balance_after_thousand_euro_was_given():
       account = BankAccount()
       account.deposit(1000)
       self.assertEquals(account.get_balance(), 1000)
    
  6. Spustite a spojazdnite test.

    Ak teraz spustíte test, tento samozrejme skončí neúspešne, keďže sa v triede BankAccount nenachádza metóda deposit(). Začneme teda jej vytvorením:

    def deposit(self, amount):
       self.amount = amount
    

    Vo výstupe testu už tentokrát síce chyba o neexistencii metódy deposit() nie je, ale tentokrát sa zobrazí chyba iná - objekt typu BankAccount nemá atribút balance:

    Error
    Traceback (most recent call last):
      File "bank_account.py", line 25, in test_balance_after_some_deposit_was_given
        account.deposit(1000)
      File "bank_account.py", line 13, in deposit
        self.balance += value
    AttributeError: 'BankAccount' object has no attribute 'balance'
    

    Budeme preto musieť vytvoriť konštruktor, v ktorom triedu BankAccount inicializujeme - vytvoríme členskú premennú balance a inicializujeme ju na hodnotu 0:

    def __init__(self):
       self.amount = 0
    
  7. Navrhnite a implementujte test, pomocou ktorého overíte, či je možné na účet vložiť záporný obnos peňazí.

    Parametrom metódy deposit() môžu byť len kladné hodnoty. Vytvoríme teda test, ktorý overí tento prípad. V prípade, že dôjde k pokusu o vloženie zápornej hodnoty na účet, metóda vyvolá výnimku ValueError.

    def test_when_negative_number_is_given_ValueError_is_thrown(self):
       account = BankAccount()
       self.assertRaises(ValueError, account.deposit, -1000)
    

    Ak teraz spustíme test, samozrejme zlyhá, keďže metóda deposit() žiadnu výnimku nevyvolá:

    Failure
    Traceback (most recent call last):
      File "bank_account.py", line 29, in test_when_negative_number_is_given_ValueError_is_thrown
        self.assertRaises(ValueError, account.deposit, -1000)
    AssertionError: ValueError not raised by deposit
    

    Aktualizujeme teda aj samotnú metódu deposit(), aby aj tento test prešiel:

    def deposit(self, value):
       if value < 0:
          raise ValueError('Amount must be greater than 0')
       self.balance += value
    

    Uvedený test je však možné zapísať aj pomocou context manager-u. Takto upravený test vyzerá nasledovne:

    def test_when_negative_number_is_given_ValueError_is_thrown(self):
       account = BankAccount()
       with self.assertRaises(ValueError):
          account.deposit(-1000)
    
  8. Podobným spôsobom navrhnite a implementujte aj zostávajúce testy pre triedu BankAccount a jej metódy.

    Vytvorte nasledovné testy:

    • Ak do metódy deposit() vstúpi parameter iného typu ako integer, metóda vyvolá výnimku TypeError.
    • Ak do metódy withdraw() vstúpi záporná hodnota, metóda vyvolá výnimku ValueError.
    • Ak do metódy withdraw() vstúpi parameter iného typu ako integer, metóda vyvolá výnimku TypeError.
    • Ak pri výbere peňazí z účtu dôjde k pokusu o výber väčšieho množstva peňazí, ako sa na účte nachádza, dôjde k vyvolaniu výnimky ValueError.
    • Ak pri prevode peňazí z účtu na iný účet tento nebude zadaný ako objekt typu BankAccount, dôjde k vyvolaniu výnimky TypeError.
    • Pri prevode peňazí z účtu na iný účet musí byť na danom účte dostatok peňazí a prenášaný obnos peňazí musí byť definovaný ako kladné celé číslo.
  9. Aktualizujte kód triedy BankAccount tak, aby prešiel vytvorenými testami.

Fixtures

Ak sa pozrieme na testovacie metódy v triede definujúcej test case, nájdeme v každej jednej z nich niečo spoločné - vytváranie nového účtu. Tento fragment teda osamostatníme a vytvoríme z neho fixture setUp().

  1. Vytvorte fixture setUp(), v rámci ktorého vytvoríte

     def setUp(self):
         self.account = BankAccount()
    
  2. Aktualizujte kód testov vzhľadom na vytvorený fixture.

Organizing Test Code

V nasledujúcich úlohách sa pokúsime reorganizovať vytvorený kód. Aktuálne máme všetky testy vytvorené v rámci jednej TestCase triedy. Budeme sa držať pravidla, že pre každú metódu triedy BankAccount vytvoríme samostatný TestCase. Vytvoríme preto nasledovné štyri triedy:

  1. triedu DepositTestCase pre otestovanie metódy deposit()
  2. triedu WithdrawTestCase pre otestovanie metódy withdraw()
  3. triedu GetBalanceTestCasepre otestovanie metódy get_balance()
  4. triedu TransferTestCase pre otestovanie metódy transfer()
  1. Vytvorte triedu DepositTestCase, ktorá bude reprezentovať test case pre testovanie metódy deposit() triedy BankAccount.

    class DepositTestCase(TestCase):
    
       def setUp(self):
          self.account = BankAccount()
    
       def test_balance_after_some_deposit_was_given(self):
          self.account.deposit(1000)
          self.assertEqual(self.account.get_balance(), 1000)
    
       def test_balance_after_two_deposits_were_given(self):
          self.account.deposit(1000)
          self.account.deposit(500)
          self.assertEqual(self.account.get_balance(), 1500)
    
       def test_ValueError_raised_after_negative_deposit(self):
          with self.assertRaises(ValueError):
              self.account.deposit(-1000)
    
  2. Vytvorte triedu WithdrawTestCase, ktorá bude reprezentovať test case pre testovanie metódy withdraw() triedy BankAccount.

  3. Vytvorte triedu GetBalanceTestCase, ktorá bude reprezentovať test case pre testovanie metódy get_balance() triedy BankAccount.

    class GetBalanceTestCase(TestCase):
    
       def test_init_balance_is_zero_after_new_ba_was_created(self):
          account = BankAccount()
          self.assertEqual(account.get_balance(), 0)
    
  4. Vytvorte triedu TransferTestCase, ktorá bude reprezentovať test case pre testovanie metódy transfer() triedy BankAccount.

Running Tests from CLI

Separate the Implementation from the Test Code

V závislosti od projektu môže štruktúra projektu vyzerať rozlične. Napr.:

project
├── bankaccount.py
└── test_bankaccount.py

Rovnako však môže vyzerať nasledovne:

project
├── bank
│   ├── __init__.py         # make it a package
│   └── bankaccount.py
└── test
    ├── __init__.py         # also make test a package
    └── test_bankaccount.py

http://stackoverflow.com/a/24266885/1671256

ToDo

xmlrunner

Resources

results matching ""

    No results matching ""