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
)
Vytvorte prázdnu triedu
BankAccountTest
, ktorá bude reprezentovaťTestCase
pre testovanie implementácie triedyBankAccount
from unittest import TestCase class BankAccountTest(TestCase): pass
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)
Spustite test.
Po spustení testu tento samozrejme zlyhá, nakoľko trieda
BankAccount
ako ani jej metódaget_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ť.Spojazdnite test.
Aby sme test spojazdnili, musíme vytvoriť ako triedu
BankAccount
, tak aj jej metóduget_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ý.
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:- vytvoríme nový účet (výška účtu bude po vytvorení 0)
- vložíme na účet 1000 eur
- 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)
Spustite a spojazdnite test.
Ak teraz spustíte test, tento samozrejme skončí neúspešne, keďže sa v triede
BankAccount
nenachádza metódadeposit()
. 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 typuBankAccount
nemá atribútbalance
: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
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ýnimkuValueError
.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)
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 akointeger
, metóda vyvolá výnimkuTypeError
. - Ak do metódy
withdraw()
vstúpi záporná hodnota, metóda vyvolá výnimkuValueError
. - Ak do metódy
withdraw()
vstúpi parameter iného typu akointeger
, metóda vyvolá výnimkuTypeError
. - 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ýnimkyTypeError
. - 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.
- Ak do metódy
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()
.
Vytvorte fixture
setUp()
, v rámci ktorého vytvorítedef setUp(self): self.account = BankAccount()
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:
- triedu
DepositTestCase
pre otestovanie metódydeposit()
- triedu
WithdrawTestCase
pre otestovanie metódywithdraw()
- triedu
GetBalanceTestCase
pre otestovanie metódyget_balance()
- triedu
TransferTestCase
pre otestovanie metódytransfer()
Vytvorte triedu
DepositTestCase
, ktorá bude reprezentovať test case pre testovanie metódydeposit()
triedyBankAccount
.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)
Vytvorte triedu
WithdrawTestCase
, ktorá bude reprezentovať test case pre testovanie metódywithdraw()
triedyBankAccount
.Vytvorte triedu
GetBalanceTestCase
, ktorá bude reprezentovať test case pre testovanie metódyget_balance()
triedyBankAccount
.class GetBalanceTestCase(TestCase): def test_init_balance_is_zero_after_new_ba_was_created(self): account = BankAccount() self.assertEqual(account.get_balance(), 0)
Vytvorte triedu
TransferTestCase
, ktorá bude reprezentovať test case pre testovanie metódytransfer()
triedyBankAccount
.
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