OOP koodi struktuureerimise näited¶
Programmeerides on mitu erinevat moodust ühe ülesande lahendamiseks. Selle peatüki eesmärk on näidata, mis on klasside eelised funktsioonide ees ning kuidas klasse struktureerida.
Artiklis lahendame ühte ülesannet korduvalt, kasutades selleks erinevaid viise.
Ülesanne
Koosta programm pangakontode loomiseks. Programmiga peab saama:
luua uusi kontosid, andes kontole mingi hulga raha
Kanda kontole raha
Kontole ei saa kanda negatiivset summat
Võtta kontolt raha
Kontolt ei saa võtta negatiivset summat
Kontolt ei saa võtta rohkem raha kui kontol on
Kanda ühelt kontolt raha teisele
Kehtivad samad reeglid, mis kontole raha kandmisel ja välja võtmisel
Kood peab olema testidega testitud
1. Lahendus kasutades funktsioone
Seda ülesannet on suhteliselt lihtne lahendada tehes mitu erinevat funktsiooni. Programmile saab anda „mälu“, salvestades kontod eraldi sõnastikku.
Oht
Järgneb kole kood, millest ei tasu õppida.
"""Bank accounts constructed with functions only"""
from typing import Dict
# Create empty dictionary for accounts.
BANK_CARDS: Dict[str, int] = {}
def create_bank_card(name: str, balance: int) -> None:
"""Create new bank card."""
BANK_CARDS[name] = balance
def get_balance(name):
"""Get balance of given account."""
return BANK_CARDS.get(name)
def deposit(name: str, amount: int) -> bool:
"""
Deposit money to bank account.
Deposit can be made if amount is not negative.
"""
if amount < 0:
return False
BANK_CARDS[name] += amount
return True
def withdraw(name: str, amount: int) -> bool:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative or withdrawal would end with negative balance
"""
if amount < 0 or BANK_CARDS[name] - amount < 0:
return False
BANK_CARDS[name] -= amount
return True
def transfer(account_from: str, account_to: str, amount: int):
"""
Transfer money from one account to the other.
If amount is negative or original account can't withdraw the money. Bank won't transfer money.
"""
return withdraw(account_from, amount) and deposit(account_to, amount)
Sellisel lahendusel on mitmeid vigu. Näiteks eeldavad kõik funktsioonid peale create_bank_card
, et kaart on juba tehtud. Ning viskavad erindi, kui sellise nimega kaarti ei ole:
>>> deposit("Mati", 10)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/pydoc_root/oop_structure/a_functions/bank.py", line 27, in deposit
BANK_CARDS[name] += amount
KeyError: 'Mati'
Samuti ei saa olla kahte kaarti, mille omanikul oleks sama nimi.
2. Lahendus kasutades klasse
Koondame meetodid klassi abil ühiseks tervikuks.
Nõuanne
Klasside kohta saab pikemalt lugeda siit.
"""Bank account logic constructed with classes."""
class Card:
def __init__(self, name: str, balance: int):
"""Creates new bank card."""
self.name = name
self._balance = balance
def get_balance(self):
return self._balance
def deposit(self, amount: int) -> bool:
"""
Deposit money to bank account.
Deposit can be made if amount is not negative.
"""
if amount < 0:
return False
self._balance += amount
return True
def withdraw(self, amount: int) -> bool:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative or withdrawal would end with negative balance
"""
if amount < 0 or self._balance - amount < 0:
return False
self._balance -= amount
return True
def transfer(self, to_account: "Card", amount: int) -> bool:
"""
Transfer money from one account to the other.
If amount is negative or original account can't withdraw the money. Bank won't transfer money.
"""
return self.withdraw(amount) and to_account.deposit(amount)
Klassi koondatult on meetodites olev loogika lihtsam. Samuti ei saa enam kutsuda meetodeid, mis eeldavad konto olemasolu ilma kontot loomata.
3. Lahendus kasutades erindeid
Hetkel tagastavad deposit()
, withdraw()
ja transfer()
tõeväärtuse sellest,
kas meetod töötas ilma takistusteta või mitte.
Selleks, et kasutaja või mõni teine programm teaks, miks tehing ei õnnestu, defineerime uue erindi IllegalTransactionException
.
Erindit on siinkohal mõistlik kasutada, kuna tehingu mitte õnnestumine on antud funktsioonide puhul ootamatu erijuht,
millega teised programmid peavad arvestama.
Nõuanne
Erindite kohta saab pikemalt lugeda siit.
"""
Bank account logic constructed with classes.
Using custom exception.
"""
class IllegalTransactionException(Exception):
pass
class Card:
def __init__(self, name: str, balance: int):
"""Creates new bank card."""
self.name = name
self._balance = balance
def get_balance(self):
return self._balance
def deposit(self, amount: int) -> None:
"""
Deposit money to bank account.
Deposit can be made if amount is not negative.
"""
if amount < 0:
raise IllegalTransactionException("Can't deposit negative amount")
self._balance += amount
def withdraw(self, amount: int) -> None:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative or withdrawal would end with negative balance
"""
if amount < 0:
raise IllegalTransactionException("Can't withdraw negative amount")
if self._balance - amount < 0:
raise IllegalTransactionException("Can't withdraw more money than account has")
self._balance -= amount
def transfer(self, to_account: "Card", amount: int) -> None:
"""
Transfer money from one account to the other.
If amount is negative or original account can't withdraw the money. Bank won't transfer money.
"""
self.withdraw(amount)
to_account.deposit(amount)
Ülesande täiendus
Ülesande tellija otsustas järsku, et tema programm peab võimaldama ka krediitkaartide kasutamist.
krediitkaartidel peab olema võimalus määrata erinevat krediidi limiiti.
krediitkaart ja deebetkaart peavad saama omavahel tehinguid teha.
4. Lahendus kaardi tüübid sõnena
Kõige lihtsama lahendusena saame muuta meie koodi ülesandele vastavaks lisades Card
objektidele juurde muutuja,
mis tähistab, kas tegu on deebet- või krediitkaardiga. Lisaks peame muutma natuke konstruktorit, ning withdraw
meetotit.
Oht
Järgneb kole kood, millest ei tasu õppida.
"""
Bank account logic constructed with classes.
Using custom exceptions and different card types (types as strings).
"""
class IllegalTransactionException(Exception):
pass
class Card:
DEBIT = "DEBIT"
CREDIT = "CREDIT"
def __init__(self, name: str, balance: int, card_type="DEBIT", credit_limit=0):
self.name = name
self._balance = balance
self.type = card_type
self._credit_limit = credit_limit
def get_balance(self):
return self._balance
def deposit(self, amount: int) -> None:
"""
Deposit money to bank account.
Deposit can be made if amount is not negative.
"""
if amount < 0:
raise IllegalTransactionException("Can't deposit negative amount")
self._balance += amount
def withdraw(self, amount: int) -> None:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative
or withdrawal would end with negative balance or exceed credit limit if card type is credit.
"""
if amount < 0:
raise IllegalTransactionException("Can't withdraw negative amount")
if self.type == Card.DEBIT and self._balance - amount < 0:
raise IllegalTransactionException("Can't withdraw more money than account has")
if self.type == Card.CREDIT and self._balance + self._credit_limit - amount < 0:
raise IllegalTransactionException("Withdrawal can't exceed credit limit")
self._balance -= amount
def transfer(self, to_account: "Card", amount: int) -> None:
"""
Transfer money from one account to the other.
If amount is negative or original account can't withdraw the money. Bank won't transfer money.
"""
self.withdraw(amount)
to_account.deposit(amount)
Selline kood töötab, kuid ei ole väga mugav lugeda ja täiendada.
Kui meetod peab käituma vastavalt objekti tüübile erinevalt, võiks olla kasutuses hoopis kaks eraldi klassi. DebitCard
ja CreditCard
.
Lahendus kaardi tüübid eraldi klassides (pärimine)
Lahendame sama ülesande, kasutades kahte eraldi klassi. CreditCard
on DebitCard
klassi alamklass.
Nõuanne
Pärimise kohta saab pikemalt lugeda siit.
"""
Bank account logic constructed with classes.
Using custom exceptions and different card types (Inheritance example).
"""
class IllegalTransactionException(Exception):
pass
class DebitCard:
def __init__(self, name, balance):
self.name = name
self._balance = balance
def get_balance(self) -> int:
return self._balance
def deposit(self, amount: int) -> None:
"""
Deposit money to bank account.
Deposit can be made if amount is not negative.
"""
if amount < 0:
raise IllegalTransactionException("Can't deposit negative amount")
self._balance += amount
def withdraw(self, amount: int) -> None:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative or withdrawal would end with negative balance
"""
if amount < 0:
raise IllegalTransactionException("Can't withdraw negative amount")
if self._balance - amount < 0:
raise IllegalTransactionException("Can't withdraw more money than account has")
self._balance -= amount
def transfer(self, to_account: "DebitCard", amount: int):
"""
Transfer money from one account to the other.
If amount is negative or original account can't withdraw the money. Bank won't transfer money.
"""
self.withdraw(amount)
to_account.deposit(amount)
class CreditCard(DebitCard):
def __init__(self, name: str, balance: int, credit_limit=1000):
super().__init__(name, balance)
self._credit_limit = credit_limit
def withdraw(self, amount: int) -> None:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative or withdrawal would exceed account credit limit
"""
if amount < 0:
raise IllegalTransactionException("Can't withdraw negative amount")
if self._balance + self._credit_limit - amount < 0:
raise IllegalTransactionException("Withdrawal can't exceed credit limit")
self._balance -= amount
Selliselt ülesannet lahendades on kood lihtsamini loetavam, ning samas välditakse koodikordusi.
Lahendus kasutades abstraktset ülemklassi
Nõuanne
Järgnev näide on abstraktsetest klassidest, mis ei ole kohustuslik materjal kursuse raames, aga on väga huvitav.
Järgnevas näites on DebitCard
ja CreditCard
leiduv ühine kood toodud abstraktsesse ülemklassi Card
.
"""
Bank account logic constructed with classes.
Using custom exceptions and different card types (Inheritance example).
Extending this sample to use abstract base class.
"""
from abc import ABC, abstractmethod
class IllegalTransactionException(Exception):
pass
class Card(ABC):
def __init__(self, name: str, balance: int):
self.name = name
self._balance = balance
def get_balance(self) -> int:
return self._balance
def deposit(self, amount: int) -> None:
"""
Deposit money to bank account.
Deposit can be made if amount is not negative.
"""
if amount < 0:
raise IllegalTransactionException("Can't deposit negative amount")
self._balance += amount
@abstractmethod # mark this method as abstract, child classes should implement it.
def withdraw(self, amount: int):
"""Withdraw money from bank account."""
pass
def transfer(self, to_account: "Card", amount: int) -> None:
"""
Transfer money from one account to the other.
If amount is negative or original account can't withdraw the money. Bank won't transfer money.
"""
self.withdraw(amount)
to_account.deposit(amount)
class DebitCard(Card):
def withdraw(self, amount: int) -> None:
if amount < 0:
raise IllegalTransactionException("Can't withdraw negative amount")
if self._balance - amount < 0:
raise IllegalTransactionException("Can't withdraw more money than account has")
self._balance -= amount
class CreditCard(Card):
def __init__(self, name: str, balance, credit_limit=1000):
super().__init__(name, balance)
self._credit_limit = credit_limit
def withdraw(self, amount: int) -> None:
"""
Withdraw money from bank account.
Withdraw can't be made if amount is negative or withdrawal would exceed account credit limit
"""
if amount < 0:
raise IllegalTransactionException("Can't withdraw negative amount")
if self._balance + self._credit_limit - amount < 0:
raise IllegalTransactionException("Withdrawal can't exceed credit limit")
self._balance -= amount
Abstraktses klassis Card
on ka defineeritud abstraktne meetod withdraw
, mille peab alamklass defineerima.
Muul juhul ei saa uusi klassi objekte luua.
>>> card = Card("Name", 100)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: Can't instantiate abstract class Card with abstract methods withdraw
Testid
from pytest import fixture
from .bank import *
@fixture(autouse=True)
def create_sample_bank_accounts():
"""
This method will be called before each test method.
See: https://docs.pytest.org/en/latest/fixture.html#fixtures
"""
create_bank_card("Johnny", 100)
create_bank_card("Joe", 200)
create_bank_card("Mary", 231)
yield
def test__create_bank_card__balance_is_right():
assert get_balance("Johnny") == 100
def test__deposit_money__balance_increases():
assert deposit("Johnny", 50)
assert get_balance("Johnny") == 150
def test__deposit_negative_amount__balance_does_not_change():
assert not deposit("Joe", -50)
assert get_balance("Joe") == 200
def test__withdraw_money__balance_decreases():
assert withdraw("Mary", 31)
assert get_balance("Mary") == 200
def test__withdraw_negative_amount__balance_does_not_change():
assert not withdraw("Mary", -3000)
assert get_balance("Mary") == 231
def test__withdraw_more_money_than_account_has__balance_does_not_change():
assert not withdraw("Mary", 3000)
assert get_balance("Mary") == 231
def test__transfer_money__from_account_balance_decreases():
assert transfer("Johnny", "Mary", 10)
assert get_balance("Johnny") == 90
def test__transfer_money__to_account_balance_increases():
assert transfer("Johnny", "Mary", 10)
assert get_balance("Mary") == 241
def test__transfer_negative_amount__from_account_balance_stays_the_same():
assert not transfer("Johnny", "Mary", -10)
assert get_balance("Johnny") == 100
def test__transfer_negative_amount__to_account_balance_stays_the_same():
assert not transfer("Johnny", "Mary", -10)
assert get_balance("Mary") == 231
def test__transfer_not_enough_money_on_from_account__from_account_balance_stays_the_same():
assert not transfer("Johnny", "Mary", 100_000_000)
assert get_balance("Johnny") == 100
def test__transfer_not_enough_money_on_from_account__to_account_balance_stays_the_same():
assert not transfer("Johnny", "Mary", 100_000_000)
assert get_balance("Mary") == 231
from pytest import fixture
from .bank import Card
class TestBase:
@fixture(autouse=True)
def create_sample_bank_accounts(self):
"""
This method will be called before each test method.
See: https://docs.pytest.org/en/latest/fixture.html#fixtures
"""
self.johnny_card = Card("Johnny", 100)
self.joe_card = Card("Joe", 200)
self.mary_card = Card("Mary", 231)
yield
class TestCreateCard(TestBase):
def test__create_bank_card__balance_is_right(self):
assert self.johnny_card.get_balance() == 100
class TestDeposit(TestBase):
def test__deposit_money__method_returns_true(self):
assert self.johnny_card.deposit(50)
def test__deposit_money__balance_increases(self):
self.johnny_card.deposit(50)
assert self.johnny_card.get_balance() == 150
def test__deposit_negative_amount__method_returns_false(self):
assert not self.johnny_card.deposit(-50)
def test__deposit_negative_amount__balance_does_not_change(self):
self.johnny_card.deposit(-50)
assert self.johnny_card.get_balance() == 100
class TestWithdraw(TestBase):
def test__withdraw_money__method_returns_true(self):
assert self.mary_card.withdraw(31)
def test__withdraw_money__balance_decreases(self):
self.mary_card.withdraw(31)
assert self.mary_card.get_balance() == 200
def test__withdraw_negative_amount__method_returns_false(self):
assert not self.mary_card.withdraw(-30)
def test__withdraw_negative_amount__balance_does_not_change(self):
self.mary_card.withdraw(-30)
assert self.mary_card.get_balance() == 231
def test__withdraw_more_money_than_account_has__method_returns_false(self):
assert not self.mary_card.withdraw(3000)
def test__withdraw_more_money_than_account_has__balance_does_not_change(self):
self.mary_card.withdraw(3000)
assert self.mary_card.get_balance() == 231
class TestTransfer(TestBase):
def test__transfer_money__method_returns_true(self):
assert Card.transfer(self.johnny_card, self.mary_card, 10)
def test__transfer_money__from_account_balance_decreases(self):
Card.transfer(self.johnny_card, self.mary_card, 10)
assert self.johnny_card.get_balance() == 90
def test__transfer_money__to_account_balance_increases(self):
Card.transfer(self.johnny_card, self.mary_card, 10)
assert self.mary_card.get_balance() == 241
def test__transfer_negative_amount__method_returns_false(self):
assert not Card.transfer(self.johnny_card, self.mary_card, -10)
def test__transfer_negative_amount__from_account_balance_stays_the_same(self):
Card.transfer(self.johnny_card, self.mary_card, -10)
assert self.johnny_card.get_balance() == 100
def test__transfer_negative_amount__to_account_balance_stays_the_same(self):
Card.transfer(self.johnny_card, self.mary_card, -10)
assert self.mary_card.get_balance() == 231
def test__transfer_not_enough_money_on_from_account__method_returns_false(self):
assert not Card.transfer(self.johnny_card, self.mary_card, 100_000_000)
def test__transfer_not_enough_money_on_from_account__from_account_balance_stays_the_same(self):
Card.transfer(self.johnny_card, self.mary_card, 100_000_000)
assert self.johnny_card.get_balance() == 100
def test__transfer_not_enough_money_on_from_account__to_account_balance_stays_the_same(self):
Card.transfer(self.johnny_card, self.mary_card, 100_000_000)
assert self.mary_card.get_balance() == 231
from pytest import fixture, raises
from .bank import Card, IllegalTransactionException
class TestBase:
@fixture(autouse=True)
def create_sample_bank_accounts(self):
"""
This method will be called before each test method.
See: https://docs.pytest.org/en/latest/fixture.html#fixtures
"""
self.johnny_card = Card("Johnny", 100)
self.joe_card = Card("Joe", 200)
self.mary_card = Card("Mary", 231)
yield
class TestCreateCard(TestBase):
def test__create_bank_card__balance_is_right(self):
assert self.johnny_card.get_balance() == 100
class TestDeposit(TestBase):
def test__deposit_money__balance_increases(self):
self.johnny_card.deposit(50)
assert self.johnny_card.get_balance() == 150
def test__deposit_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_card.deposit(-50)
def test__deposit_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_card.deposit(-50)
assert self.johnny_card.get_balance() == 100
class TestWithdraw(TestBase):
def test__withdraw_money__balance_decreases(self):
self.mary_card.withdraw(31)
assert self.mary_card.get_balance() == 200
def test__withdraw_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
def test__withdraw_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
assert self.mary_card.get_balance() == 231
def test__withdraw_more_money_than_account_has__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
def test__withdraw_more_money_than_account_has__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
assert self.mary_card.get_balance() == 231
class TestTransfer(TestBase):
def test__transfer_money__from_account_balance_decreases(self):
Card.transfer(self.johnny_card, self.mary_card, 10)
assert self.johnny_card.get_balance() == 90
def test__transfer_money__to_account_balance_increases(self):
Card.transfer(self.johnny_card, self.mary_card, 10)
assert self.mary_card.get_balance() == 241
def test__transfer_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_card, self.mary_card, -10)
def test__transfer_negative_amount__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_card, self.mary_card, -10)
assert self.johnny_card.get_balance() == 100
def test__transfer_negative_amount__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_card, self.mary_card, -10)
assert self.mary_card.get_balance() == 231
def test__transfer_not_enough_money_on_from_account__method_raises_exception(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_card, self.mary_card, 100_000_000)
def test__transfer_not_enough_money_on_from_account__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_card, self.mary_card, 100_000_000)
assert self.johnny_card.get_balance() == 100
def test__transfer_not_enough_money_on_from_account__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_card, self.mary_card, 100_000_000)
assert self.mary_card.get_balance() == 231
from pytest import fixture, raises
from .bank import Card, IllegalTransactionException
class TestBase:
@fixture(autouse=True)
def create_sample_bank_accounts(self):
"""
This method will be called before each test method.
See: https://docs.pytest.org/en/latest/fixture.html#fixtures
"""
self.johnny_debit_card = Card("Johnny", 100)
self.johnny_credit_card = Card("Johnny", 1, card_type=Card.CREDIT, credit_limit=1000)
self.joe_card = Card("Joe", 200)
self.mary_card = Card("Mary", 231)
yield
class TestCreateCard(TestBase):
def test__create_debit_card__balance_is_right(self):
assert self.johnny_debit_card.get_balance() == 100
def test__create_credit_card__balance_is_right(self):
assert self.johnny_credit_card.get_balance() == 1
class TestDeposit(TestBase):
def test__deposit_money__balance_increases(self):
self.johnny_debit_card.deposit(50)
assert self.johnny_debit_card.get_balance() == 150
def test__deposit_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_debit_card.deposit(-50)
def test__deposit_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_debit_card.deposit(-50)
assert self.johnny_debit_card.get_balance() == 100
class TestWithdraw(TestBase):
def test__withdraw_debit_card__balance_decreases(self):
self.mary_card.withdraw(31)
assert self.mary_card.get_balance() == 200
def test__withdraw_credit_card__balance_can_be_negative(self):
self.johnny_credit_card.withdraw(21)
assert self.johnny_credit_card.get_balance() == -20
def test__withdraw_debit_card_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
def test__withdraw_credit_card_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(-30)
def test__withdraw_debit_card_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
assert self.mary_card.get_balance() == 231
def test__withdraw_credit_card_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(-30)
assert self.johnny_credit_card.get_balance() == 1
def test__withdraw_debit_card_more_money_than_account_has__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
def test__withdraw_debit_card_more_money_than_account_has__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
assert self.mary_card.get_balance() == 231
def test__withdraw_credit_card_exceeding_credit_limit__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(3000)
def test__withdraw_credit_card_exceeding_credit_limit__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(3000)
assert self.johnny_credit_card.get_balance() == 1
class TestTransfer(TestBase):
def test__transfer_money__from_account_balance_decreases(self):
Card.transfer(self.johnny_debit_card, self.mary_card, 10)
assert self.johnny_debit_card.get_balance() == 90
def test__transfer_money__to_account_balance_increases(self):
Card.transfer(self.johnny_debit_card, self.mary_card, 10)
assert self.mary_card.get_balance() == 241
def test__transfer_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, -10)
def test__transfer_negative_amount__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, -10)
assert self.johnny_debit_card.get_balance() == 100
def test__transfer_negative_amount__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, -10)
assert self.mary_card.get_balance() == 231
def test__transfer_not_enough_money_on_from_account__method_raises_exception(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
def test__transfer_not_enough_money_on_from_account__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
assert self.johnny_debit_card.get_balance() == 100
def test__transfer_not_enough_money_on_from_account__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
assert self.mary_card.get_balance() == 231
from pytest import fixture, raises
from .bank import DebitCard, CreditCard, IllegalTransactionException
class TestBase:
@fixture(autouse=True)
def create_sample_bank_accounts(self):
"""
This method will be called before each test method.
See: https://docs.pytest.org/en/latest/fixture.html#fixtures
"""
self.johnny_debit_card = DebitCard("Johnny", 100)
self.johnny_credit_card = CreditCard("Johnny", 1, credit_limit=1000)
self.joe_card = DebitCard("Joe", 200)
self.mary_card = DebitCard("Mary", 231)
yield
class TestCreateCard(TestBase):
def test__create_debit_card__balance_is_right(self):
assert self.johnny_debit_card.get_balance() == 100
def test__create_credit_card__balance_is_right(self):
assert self.johnny_credit_card.get_balance() == 1
class TestDeposit(TestBase):
def test__deposit_money__balance_increases(self):
self.johnny_debit_card.deposit(50)
assert self.johnny_debit_card.get_balance() == 150
def test__deposit_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_debit_card.deposit(-50)
def test__deposit_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_debit_card.deposit(-50)
assert self.johnny_debit_card.get_balance() == 100
class TestWithdraw(TestBase):
def test__withdraw_debit_card__balance_decreases(self):
self.mary_card.withdraw(31)
assert self.mary_card.get_balance() == 200
def test__withdraw_credit_card__balance_can_be_negative(self):
self.johnny_credit_card.withdraw(21)
assert self.johnny_credit_card.get_balance() == -20
def test__withdraw_debit_card_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
def test__withdraw_credit_card_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(-30)
def test__withdraw_debit_card_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
assert self.mary_card.get_balance() == 231
def test__withdraw_credit_card_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(-30)
assert self.johnny_credit_card.get_balance() == 1
def test__withdraw_debit_card_more_money_than_account_has__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
def test__withdraw_debit_card_more_money_than_account_has__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
assert self.mary_card.get_balance() == 231
def test__withdraw_credit_card_exceeding_credit_limit__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(3000)
def test__withdraw_credit_card_exceeding_credit_limit__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(3000)
assert self.johnny_credit_card.get_balance() == 1
class TestTransfer(TestBase):
def test__transfer_money__from_account_balance_decreases(self):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, 10)
assert self.johnny_debit_card.get_balance() == 90
def test__transfer_money__to_account_balance_increases(self):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, 10)
assert self.mary_card.get_balance() == 241
def test__transfer_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
assert not DebitCard.transfer(self.johnny_debit_card, self.mary_card, -10)
def test__transfer_negative_amount__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, -10)
assert self.johnny_debit_card.get_balance() == 100
def test__transfer_negative_amount__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, -10)
assert self.mary_card.get_balance() == 231
def test__transfer_not_enough_money_on_from_account__method_raises_exception(self):
with raises(IllegalTransactionException):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
def test__transfer_not_enough_money_on_from_account__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
assert self.johnny_debit_card.get_balance() == 100
def test__transfer_not_enough_money_on_from_account__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
DebitCard.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
assert self.mary_card.get_balance() == 231
from pytest import fixture, raises
from .bank import Card, DebitCard, CreditCard, IllegalTransactionException
class TestBase:
@fixture(autouse=True)
def create_sample_bank_accounts(self):
"""
This method will be called before each test method.
See: https://docs.pytest.org/en/latest/fixture.html#fixtures
"""
self.johnny_debit_card = DebitCard("Johnny", 100)
self.johnny_credit_card = CreditCard("Johnny", 1, credit_limit=1000)
self.joe_card = DebitCard("Joe", 200)
self.mary_card = DebitCard("Mary", 231)
yield
class TestCreateCard(TestBase):
def test__create_debit_card__balance_is_right(self):
assert self.johnny_debit_card.get_balance() == 100
def test__create_credit_card__balance_is_right(self):
assert self.johnny_credit_card.get_balance() == 1
class TestDeposit(TestBase):
def test__deposit_money__balance_increases(self):
self.johnny_debit_card.deposit(50)
assert self.johnny_debit_card.get_balance() == 150
def test__deposit_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_debit_card.deposit(-50)
def test__deposit_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_debit_card.deposit(-50)
assert self.johnny_debit_card.get_balance() == 100
class TestWithdraw(TestBase):
def test__withdraw_debit_card__balance_decreases(self):
self.mary_card.withdraw(31)
assert self.mary_card.get_balance() == 200
def test__withdraw_credit_card__balance_can_be_negative(self):
self.johnny_credit_card.withdraw(21)
assert self.johnny_credit_card.get_balance() == -20
def test__withdraw_debit_card_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
def test__withdraw_credit_card_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(-30)
def test__withdraw_debit_card_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(-30)
assert self.mary_card.get_balance() == 231
def test__withdraw_credit_card_negative_amount__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(-30)
assert self.johnny_credit_card.get_balance() == 1
def test__withdraw_debit_card_more_money_than_account_has__method_raises_exception(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
def test__withdraw_debit_card_more_money_than_account_has__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.mary_card.withdraw(3000)
assert self.mary_card.get_balance() == 231
def test__withdraw_credit_card_exceeding_credit_limit__method_raises_exception(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(3000)
def test__withdraw_credit_card_exceeding_credit_limit__balance_does_not_change(self):
with raises(IllegalTransactionException):
self.johnny_credit_card.withdraw(3000)
assert self.johnny_credit_card.get_balance() == 1
class TestTransfer(TestBase):
def test__transfer_money__from_account_balance_decreases(self):
Card.transfer(self.johnny_debit_card, self.mary_card, 10)
assert self.johnny_debit_card.get_balance() == 90
def test__transfer_money__to_account_balance_increases(self):
Card.transfer(self.johnny_debit_card, self.mary_card, 10)
assert self.mary_card.get_balance() == 241
def test__transfer_negative_amount__method_raises_exception(self):
with raises(IllegalTransactionException):
assert not Card.transfer(self.johnny_debit_card, self.mary_card, -10)
def test__transfer_negative_amount__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, -10)
assert self.johnny_debit_card.get_balance() == 100
def test__transfer_negative_amount__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, -10)
assert self.mary_card.get_balance() == 231
def test__transfer_not_enough_money_on_from_account__method_raises_exception(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
def test__transfer_not_enough_money_on_from_account__from_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
assert self.johnny_debit_card.get_balance() == 100
def test__transfer_not_enough_money_on_from_account__to_account_balance_stays_the_same(self):
with raises(IllegalTransactionException):
Card.transfer(self.johnny_debit_card, self.mary_card, 100_000_000)
assert self.mary_card.get_balance() == 231