Automated Teller Machine


I want to make an Automated Teller Machine that allows withdrawals or denies them, if the inputs are

  • is the PIN correct?

  • is the amount I want to take, smaller or bigger than what is in the account?

this is the truth table I get

PIN

money

withdrawal

right PIN

enough money

CASH

right PIN

NOT enough money

DENIED

wrong PIN

enough money

DENIED

wrong PIN

NOT enough money

DENIED

preview

These are the tests I have at the end of the chapter

  1import src.atm
  2import unittest
  3
  4
  5DENIED = 'DENIED'
  6
  7
  8class TestATM(unittest.TestCase):
  9
 10    def test_right_pin_enough_money_w_card(self):
 11        self.assertEqual(
 12            src.atm.withdraw(
 13                right_pin=True,
 14                enough_money=True,
 15                above_daily_limit=True,
 16                card_expired=True,
 17            ),
 18            DENIED
 19        )
 20
 21        self.assertEqual(
 22            src.atm.withdraw(
 23                right_pin=True,
 24                enough_money=True,
 25                above_daily_limit=True,
 26                card_expired=False,
 27            ),
 28            DENIED
 29        )
 30
 31        self.assertEqual(
 32            src.atm.withdraw(
 33                right_pin=True,
 34                enough_money=True,
 35                above_daily_limit=False,
 36                card_expired=True,
 37            ),
 38            DENIED
 39        )
 40
 41        self.assertEqual(
 42            src.atm.withdraw(
 43                right_pin=True,
 44                enough_money=True,
 45                above_daily_limit=False,
 46                card_expired=False,
 47            ),
 48            'CASH'
 49        )
 50
 51    def test_right_pin_not_enough_money_w_card(self):
 52        self.assertEqual(
 53            src.atm.withdraw(
 54                right_pin=True,
 55                enough_money=False,
 56                above_daily_limit=True,
 57                card_expired=True,
 58            ),
 59            DENIED
 60        )
 61
 62        self.assertEqual(
 63            src.atm.withdraw(
 64                right_pin=True,
 65                enough_money=False,
 66                above_daily_limit=True,
 67                card_expired=False,
 68            ),
 69            DENIED
 70        )
 71
 72        self.assertEqual(
 73            src.atm.withdraw(
 74                right_pin=True,
 75                enough_money=False,
 76                above_daily_limit=False,
 77                card_expired=True,
 78            ),
 79            DENIED
 80        )
 81
 82        self.assertEqual(
 83            src.atm.withdraw(
 84                right_pin=True,
 85                enough_money=False,
 86                above_daily_limit=False,
 87                card_expired=True,
 88            ),
 89            DENIED
 90        )
 91
 92    def test_wrong_pin_enough_money_w_card(self):
 93        self.assertEqual(
 94            src.atm.withdraw(
 95                right_pin=False,
 96                enough_money=True,
 97                above_daily_limit=True,
 98                card_expired=True,
 99            ),
100            DENIED
101        )
102
103        self.assertEqual(
104            src.atm.withdraw(
105                right_pin=False,
106                enough_money=True,
107                above_daily_limit=True,
108                card_expired=False,
109            ),
110            DENIED
111        )
112
113        self.assertEqual(
114            src.atm.withdraw(
115                right_pin=False,
116                enough_money=True,
117                above_daily_limit=False,
118                card_expired=True,
119            ),
120            DENIED
121        )
122
123        self.assertEqual(
124            src.atm.withdraw(
125                right_pin=False,
126                enough_money=True,
127                above_daily_limit=False,
128                card_expired=False,
129            ),
130            DENIED
131        )
132
133    def test_wrong_pin_not_enough_money_w_card(self):
134        self.assertEqual(
135            src.atm.withdraw(
136                right_pin=False,
137                enough_money=False,
138                above_daily_limit=True,
139                card_expired=True,
140            ),
141            DENIED
142        )
143
144        self.assertEqual(
145            src.atm.withdraw(
146                right_pin=False,
147                enough_money=False,
148                above_daily_limit=True,
149                card_expired=False,
150            ),
151            DENIED
152        )
153
154        self.assertEqual(
155            src.atm.withdraw(
156                right_pin=False,
157                enough_money=False,
158                above_daily_limit=False,
159                card_expired=True,
160            ),
161            DENIED
162        )
163
164        self.assertEqual(
165            src.atm.withdraw(
166                right_pin=False,
167                enough_money=False,
168                above_daily_limit=False,
169                card_expired=False,
170            ),
171            DENIED
172        )
173
174
175# Exceptions seen
176# AssertionError
177# NameError
178# AttributeError
179# TypeError
180# SyntaxError

requirements


start the project

  • I name this project atm

  • I open a terminal

  • I use uv to make a directory for the project and initialize it

    uv init atm
    

    the terminal shows

    Initialized project `atm` at `.../pumping_python/atm`
    

    then goes back to the command line.

  • I change directory to the project

    cd atm
    

    the terminal shows I am in the atm folder

    .../pumping_python/atm
    
  • I make a directory for the source code

    mkdir src
    

    the terminal goes back to the command line.

  • I use the mv program to change the name of main.py to atm.py and move it to the src folder

    mv main.py src/atm.py
    
    Move-Item main.py src/atm.py
    

    the terminal goes back to the command line.

  • I make a directory for the tests

    mkdir tests
    

    the terminal goes back to the command line.

  • I make the tests directory a Python package

    Danger

    use 2 underscores (__) before and after init for __init__.py not _init_.py

    touch tests/__init__.py
    
    New-Item tests/__init__.py
    

    the terminal goes back to the command line.

  • I make a Python file for the tests in the tests directory

    touch tests/test_atm.py
    
    New-Item tests/test_atm.py
    

    the terminal goes back to the command line.

  • I open test_atm.py in the editor of the Integrated Development Environment (IDE)

    Tip

    I can open a file from the terminal in the Integrated Development Environment (IDE) with the name of the program and the name of the file. That means if I type this in the terminal

    code tests/test_atm.py
    

    Visual Studio Code opens test_atm.py in the editor

  • I add the first failing test to test_atm.py

    1import unittest
    2
    3
    4class TestATM(unittest.TestCase):
    5
    6    def test_failure(self):
    7        self.assertFalse(True)
    
  • I go back to the terminal to make a requirements file for the Python packages I need

    echo "pytest" > requirements.txt
    

    the terminal goes back to the command line.

  • I add pytest-watcher to the requirements file

    echo "pytest-watcher" >> requirements.txt
    

    the terminal goes back to the command line.

  • I install the Python packages that I wrote in the requirements file

    uv add --requirement requirements.txt
    

    the terminal shows that it installed the Python packages

  • I use tree to look at the structure of the project

    tree
    

    the terminal shows

    .
    ├── README.md
    ├── pyproject.toml
    ├── requirements.txt
    ├── src
       └── atm.py
    ├── tests
       ├── __init__.py
       └── test_atm.py
    └── uv.lock
    

    if you do not see uv.lock in your tree, make sure you ran uv add --requirement requirements.txt, then run the tests next

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal is my friend, and shows AssertionError

    ======================== FAILURES ========================
    _________________ TestATM.test_failure ___________________
    
    self = <tests.test_atm.TestATM testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_atm.py:7: AssertionError
    ================ short test summary info =================
    FAILED tests/test_atm.py::TestATM::test_failure - AssertionError: True is not false
    =================== 1 failed in X.YZs ====================
    

    because True is NOT False

    if the terminal does not show the same error, then check

    • if your tests/__init__.py has two underscores (__) before and after init for __init__.py not _init_.py

    • if you ran echo "pytest-watcher" >> requirements.txt, to add pytest-watcher to the requirements file

    fix those errors and try to run uv run pytest-watcher . --now again

  • I add AssertionError to the list of Exceptions seen in test_atm.py

     4class TestATM(unittest.TestCase):
     5
     6    def test_failure(self):
     7        self.assertFalse(True)
     8
     9
    10# Exceptions seen
    11# AssertionError
    
  • then I change True to False in the assertion

    7        self.assertFalse(False)
    

    the test passes.


test_right_pin_enough_money


RED: make it fail


I change test_failure to test_right_pin_enough_money, then add an assertion for when the right PIN is entered and there is enough money in the account

PIN

money

withdrawal

right PIN

enough money

CASH

 4class TestATM(unittest.TestCase):
 5
 6    def test_right_pin_enough_money(self):
 7        my_expectation = 'CASH'
 8        reality = src.atm.withdraw(
 9            right_pin=True,
10            enough_money=True,
11        )
12        self.assertEqual(reality, my_expectation)
13
14
15# Exceptions seen
16# AssertionError

the terminal is my friend, and shows NameError

NameError: name 'src' is not defined

because I do not have a definition for src in this file


GREEN: make it pass


  • I add NameError to the list of Exceptions seen

    15# Exceptions seen
    16# AssertionError
    17# NameError
    
  • I add an import statement at the top of the file so that I can test atm.py from the src folder

    1import src.atm
    2import unittest
    3
    4
    5class TestATM(unittest.TestCase):
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.atm' has no attribute 'withdraw'
    

    because atm.py in the src folder does not have anything named withdraw in it

    If you get ModuleNotFoundError

    ModuleNotFoundError: No module named 'src'
    

    check if you have __init__.py in the tests folder with underscores (__) before and after init for __init__.py not _init_.py, then add ModuleNotFoundError to the list of Exceptions seen

  • I add AttributeError to the list of Exceptions seen

    16# Exceptions seen
    17# AssertionError
    18# NameError
    19# AttributeError
    
  • I use the Explorer to open atm.py from the src folder in the editor

  • I add a function to atm.py

    1def withdraw():
    2    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: withdraw() got an unexpected keyword argument 'right_pin'
    

    because the test called the withdraw function with two keyword arguments and this definition only takes calls with 0 arguments

  • I add TypeError to the list of Exceptions seen in test_atm.py

    16# Exceptions seen
    17# AssertionError
    18# NameError
    19# AttributeError
    20# TypeError
    
  • I add the keyword argument to the function in atm.py

    1def withdraw(right_pin):
    2    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: withdraw() got an unexpected keyword argument 'enough_money'
    

    because the test called the withdraw function with two keyword arguments and this definition only takes calls with 1 input

  • I add enough_money to the function signature

    1def withdraw(right_pin, enough_money):
    2    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != 'CASH'
    

    the withdraw function always returns None and the assertion expects 'CASH'

  • I change the return statement to give me 'CASH'

    1def withdraw(right_pin, enough_money):
    2    return 'CASH'
    

    the test passes, this ATM works. The withdraw function always returns ‘CASH’, it does not care about the inputs. Is this Tautology or ‘CASH’ that never ends?


test_right_pin_not_enough_money


RED: make it fail


  • I add a test with an assertion for when the right PIN is entered AND there is NOT enough money in the account

    PIN

    money

    withdrawal

    right PIN

    NOT enough money

    DENIED

     7    def test_right_pin_enough_money(self):
     8        my_expectation = 'CASH'
     9        reality = src.atm.withdraw(
    10            right_pin=True,
    11            enough_money=True,
    12        )
    13        self.assertEqual(reality, my_expectation)
    14
    15    def test_right_pin_not_enough_money(self):
    16        my_expectation = 'DENIED'
    17        reality = src.atm.withdraw(
    18            right_pin=True,
    19            enough_money=False,
    20        )
    21        self.assertEqual(reality, my_expectation)
    22
    23
    24# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'CASH' != 'DENIED'
    

    because the withdraw function returns ‘CASH’ and the assertion expects ‘DENIED’


GREEN: make it pass


I add an if statement to atm.py

1def withdraw(right_pin, enough_money):
2    if enough_money == False:
3        return 'DENIED'
4
5    return 'CASH'

the test passes.


REFACTOR: make it better


  • I use the bool built-in function

    1def withdraw(right_pin, enough_money):
    2    # if enough_money == False:
    3    if bool(enough_money) == False:
    4        return 'DENIED'
    5
    6    return 'CASH'
    

    the test is still green.

  • I use Logical Negation (NOT) to write the if statement in terms of True

    1def withdraw(right_pin, enough_money):
    2    # if enough_money == False:
    3    # if bool(enough_money) == False:
    4    if not bool(enough_money) == True:
    5        return 'DENIED'
    6
    7    return 'CASH'
    

    still green.

  • I remove == True

    1def withdraw(right_pin, enough_money):
    2    # if enough_money == False:
    3    # if bool(enough_money) == False:
    4    # if not bool(enough_money) == True:
    5    if not bool(enough_money):
    6        return 'DENIED'
    7
    8    return 'CASH'
    

    green.

  • I remove bool

    1def withdraw(right_pin, enough_money):
    2    # if enough_money == False:
    3    # if bool(enough_money) == False:
    4    # if not bool(enough_money) == True:
    5    # if not bool(enough_money):
    6    if not enough_money:
    7        return 'DENIED'
    8
    9    return 'CASH'
    

    still green, because if bool(something) == False is the same as if not bool(something) == True is the same as if not something

  • I remove the commented lines

    1def withdraw(right_pin, enough_money):
    2    if not enough_money:
    3        return 'DENIED'
    4
    5    return 'CASH'
    

    the test is still green.

This is what happens when the withdraw function is called

  • it returns ‘DENIED’ if there is NOT enough money in the account

  • it gives me ‘CASH’ if the :ref:`condition<if statements>`is NOT met

What binary operation is the withdraw function using?


test_wrong_pin_enough_money


RED: make it fail


I add a test with an assertion for when the wrong PIN is entered and there is enough money in the account, to test_atm.py

PIN

money

withdrawal

wrong PIN

enough money

DENIED

 7    def test_right_pin_not_enough_money(self):
 8        my_expectation = 'DENIED'
 9        reality = src.atm.withdraw(
10            right_pin=True,
11            enough_money=False,
12        )
13        self.assertEqual(reality, my_expectation)
14
15    def test_wrong_pin_enough_money(self):
16        my_expectation = 'DENIED'
17        reality = src.atm.withdraw(
18            right_pin=False,
19            enough_money=True,
20        )
21        self.assertEqual(reality, my_expectation)
22
23
24# Exceptions seen

the terminal is my friend, and shows AssertionError

AssertionError: 'CASH' != 'DENIED'

because the withdraw function returned ‘CASH’ and the assertion expects DENIED. Why is this ATM giving ‘CASH’ when the wrong PIN is entered?


GREEN: make it pass


I add an if statement for this case to atm.py

1def withdraw(right_pin, enough_money):
2    if right_pin == False:
3        return 'DENIED'
4
5    if not enough_money:
6        return 'DENIED'
7
8    return 'CASH'

the test passes.


REFACTOR: make it better


  • I add the bool built-in function

    1def withdraw(right_pin, enough_money):
    2    # if right_pin == False:
    3    if bool(right_pin) == False:
    4        return 'DENIED'
    5
    6    if not enough_money:
    7        return 'DENIED'
    8
    9    return 'CASH'
    

    the test is still green.

  • I use Logical Negation (NOT) to write the if statement in terms of True

     1def withdraw(right_pin, enough_money):
     2    # if right_pin == False:
     3    # if bool(right_pin) == False:
     4    if not bool(right_pin) == True:
     5        return 'DENIED'
     6
     7    if not enough_money:
     8        return 'DENIED'
     9
    10    return 'CASH'
    

    still green.

  • I remove == True

     1def withdraw(right_pin, enough_money):
     2    # if right_pin == False:
     3    # if bool(right_pin) == False:
     4    # if not bool(right_pin) == True:
     5    if not bool(right_pin):
     6        return 'DENIED'
     7
     8    if not enough_money:
     9        return 'DENIED'
    10
    11    return 'CASH'
    

    green.

  • I remove bool

     1def withdraw(right_pin, enough_money):
     2    # if right_pin == False:
     3    # if bool(right_pin) == False:
     4    # if not bool(right_pin) == True:
     5    # if not bool(right_pin):
     6    if not right_pin:
     7        return 'DENIED'
     8
     9    if not enough_money:
    10        return 'DENIED'
    11
    12    return 'CASH'
    

    still green, because if bool(something) == False is the same as if not bool(something) == True is the same as if not something

  • I remove the commented lines

    1def withdraw(right_pin, enough_money):
    2    if not right_pin:
    3        return 'DENIED'
    4
    5    if not enough_money:
    6        return 'DENIED'
    7
    8    return 'CASH'
    

    this is what happens when the withdraw function is called

    • it returns ‘DENIED’ if the wrong PIN is entered

    • it returns ‘DENIED’ if there is NOT enough money in the account

    • it gives me ‘CASH’ if the above conditions are NOT met

    What binary operation is the withdraw function using now?


test_wrong_pin_not_enough_money

I add a test with an assertion for the last case, which is when the wrong PIN is entered and there is NOT enough money in the account, to test_atm.py

PIN

money

withdrawal

wrong PIN

NOT enough money

DENIED

22    def test_wrong_pin_enough_money(self):
23        my_expectation = 'DENIED'
24        reality = src.atm.withdraw(
25            right_pin=False,
26            enough_money=True,
27        )
28        self.assertEqual(reality, my_expectation)
29
30        reality = src.atm.withdraw(
31            right_pin=False,
32            enough_money=False,
33        )
34        self.assertEqual(reality, my_expectation)
35
36
37# Exceptions seen

the test is still green.


REFACTOR: make it better


  • I add a global variable to remove repetition of ‘DENIED’ from the tests because 3 of them use it, in test_atm.py

    1import src.atm
    2import unittest
    3
    4
    5DENIED = 'DENIED'
    6
    7
    8class TestATM(unittest.TestCase):
    

    this way I do not need to use a variable in each test, they can all use the global variable

  • I use the new global variable in test_right_pin_not_enough_money_w_limit

    34    def test_right_pin_not_enough_money_w_limit(self):
    35        # my_expectation = 'DENIED'
    36        reality = src.atm.withdraw(
    37            right_pin=False,
    38            enough_money=False,
    39        )
    40        # self.assertEqual(reality, my_expectation)
    41        self.assertEqual(reality, DENIED)
    42
    43
    44# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    34    def test_right_pin_not_enough_money_w_limit(self):
    35        reality = src.atm.withdraw(
    36            right_pin=False,
    37            enough_money=False,
    38        )
    39        self.assertEqual(reality, DENIED)
    40
    41
    42# Exceptions seen
    
  • I use the new global variable in test_wrong_pin_enough_money

    26    def test_wrong_pin_enough_money(self):
    27        # my_expectation = 'DENIED'
    28        reality = src.atm.withdraw(
    29            right_pin=False,
    30            enough_money=True,
    31        )
    32        # self.assertEqual(reality, my_expectation)
    33        self.assertEqual(reality, DENIED)
    34
    35    def test_right_pin_not_enough_money_w_limit(self):
    

    still green.

  • I remove the commented lines

    26    def test_wrong_pin_enough_money(self):
    27        reality = src.atm.withdraw(
    28            right_pin=False,
    29            enough_money=True,
    30        )
    31        self.assertEqual(reality, DENIED)
    32
    33    def test_right_pin_not_enough_money_w_limit(self):
    
  • I use the global variable in test_right_pin_not_enough_money

    18    def test_right_pin_not_enough_money(self):
    19        # my_expectation = 'DENIED'
    20        reality = src.atm.withdraw(
    21            right_pin=True,
    22            enough_money=False,
    23        )
    24        # self.assertEqual(reality, my_expectation)
    25        self.assertEqual(reality, DENIED)
    26
    27    def test_wrong_pin_enough_money(self):
    

    green.

  • I remove the commented lines

    18    def test_right_pin_not_enough_money(self):
    19        reality = src.atm.withdraw(
    20            right_pin=True,
    21            enough_money=False,
    22        )
    23        self.assertEqual(reality, DENIED)
    24
    25    def test_wrong_pin_enough_money(self):
    

test_right_pin_enough_money_w_limit

So far, the truth table for the Automated Teller Machine is

PIN

money

withdrawal

right PIN

enough money

CASH

right PIN

NOT enough money

DENIED

wrong PIN

enough money

DENIED

wrong PIN

NOT enough money

DENIED

I want to add a condition for a daily limit on how much can be taken from the account. The inputs for the ATM will then be

  • is the PIN correct?

  • is the amount I want to take, smaller or bigger than what is in the account?

  • will the withdrawal put the account above or below the daily limit for withdrawals?

The truth table for if the right PIN is entered and there is enough money in the account, is

PIN

money

daily limit

withdrawal

right PIN

enough money

above limit

DENIED

right PIN

enough money

NOT above limit

CASH


RED: make it fail


I add an assertion for the case where the right PIN is entered, there is enough money in the account, and it is above limit for daily withdrawals, to test_right_pin_enough_money

PIN

money

daily limit

withdrawal

right PIN

enough money

above limit

DENIED

 7    def test_right_pin_enough_money(self):
 8        reality = src.atm.withdraw(
 9            right_pin=True,
10            enough_money=True,
11            above_daily_limit=True,
12        )
13        self.assertEqual(reality, DENIED)
14
15        my_expectation = 'CASH'
16        reality = src.atm.withdraw(
17            right_pin=True,
18            enough_money=True,
19        )
20        self.assertEqual(reality, my_expectation)
21
22    def test_right_pin_not_enough_money(self):

the terminal is my friend, and shows TypeError

TypeError: withdraw() got an unexpected keyword argument 'above_daily_limit'

because the withdraw function only takes 2 arguments (right_pin and enough_money) and the new assertion called it with 3 arguments (right_pin, enough_money and above_daily_limit)


GREEN: make it pass


  • I add above_daily_limit to the function in atm.py

     1def withdraw(
     2        right_pin, enough_money,
     3        above_daily_limit,
     4    ):
     5    if not right_pin:
     6        return 'DENIED'
     7
     8    if not enough_money:
     9        return 'DENIED'
    10
    11    return 'CASH'
    

    the terminal is my friend, and shows

    • AssertionError

      AssertionError: 'CASH' != 'DENIED'
      

      because the withdraw function returned ‘CASH’ and the assertion expects DENIED.

    • TypeError

      FAILED ...test_right_pin_not_enough_money - TypeError: withdraw() missing 1 required positional argument: 'above_daily_limit'
      FAILED ...test_wrong_pin_enough_money - TypeError: withdraw() missing 1 required positional argument: 'above_daily_limit'
      FAILED ...test_right_pin_not_enough_money_w_limit - TypeError: withdraw() missing 1 required positional argument: 'above_daily_limit'
      

      because the other assertions do not provide a value for above_daily_limit when they call the withdraw function, I have to make it a choice

  • I add a default value for the above_daily_limit parameter to make it a choice

    1def withdraw(
    2        right_pin, enough_money,
    3        above_daily_limit=False,
    4    ):
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'CASH' != 'DENIED'
    

    because the withdraw function returns ‘CASH’ and the assertion expects ‘DENIED’

  • I add an if statement for this case

     1def withdraw(
     2        right_pin, enough_money,
     3        above_daily_limit=False,
     4    ):
     5    if above_daily_limit == True:
     6        return 'DENIED'
     7
     8    if not right_pin:
     9        return 'DENIED'
    10
    11    if not enough_money:
    12        return 'DENIED'
    13
    14    return 'CASH'
    

    the test passes.


REFACTOR: make it better


  • I add the bool built-in function

    5    # if above_daily_limit == True:
    6    if bool(above_daily_limit) == True:
    7        return 'DENIED'
    

    the test is still green.

  • I remove == True

    5    # if above_daily_limit == True:
    6    # if bool(above_daily_limit) == True:
    7    if bool(above_daily_limit):
    8        return 'DENIED'
    

    still green.

  • I remove bool

    1    # if above_daily_limit == True:
    2    # if bool(above_daily_limit) == True:
    3    # if bool(above_daily_limit):
    4    if above_daily_limit:
    5        return 'DENIED'
    

    green, because if bool(something) == True is the same as if bool(something) is the same as if something

  • I add a variable to use them to remove repetition of 'DENIED'

    1def withdraw(
    2        right_pin, enough_money,
    3        above_daily_limit=False,
    4    ):
    5    denied = 'DENIED'
    6    # if above_daily_limit == True:
    
  • I use the variable to remove repetition of 'DENIED'

     1def withdraw(
     2        right_pin, enough_money,
     3        above_daily_limit=False,
     4    ):
     5    denied = 'DENIED'
     6    # if above_daily_limit == True:
     7    # if bool(above_daily_limit) == True:
     8    # if bool(above_daily_limit):
     9    if above_daily_limit:
    10        # return 'DENIED'
    11        return denied
    12
    13    if not right_pin:
    14        # return 'DENIED'
    15        return denied
    16
    17    if not enough_money:
    18        # return 'DENIED'
    19        return denied
    20
    21    return 'CASH'
    
  • I remove the commented lines

     1def withdraw(
     2        right_pin, enough_money,
     3        above_daily_limit=False,
     4    ):
     5    denied = 'DENIED'
     6
     7    if above_daily_limit:
     8        return denied
     9
    10    if not right_pin:
    11        return denied
    12
    13    if not enough_money:
    14        return denied
    15
    16    return 'CASH'
    

    this is what happens when the withdraw function is called

    • it returns ‘DENIED’ if the account is above limit for daily withdrawals

    • it returns ‘DENIED’ if the wrong PIN is entered

    • it returns ‘DENIED’ if there is NOT enough money in the account

    • it gives me ‘CASH’ if the above conditions are NOT met


REFACTOR: make it better


  • I do not need to add anything to the next assertion which is for when the right PIN is entered, and there is enough money in the account, and it is NOT above limit for daily withdrawals, because the default value for the above_daily_limit parameter of the withdraw function is False

    PIN

    money

    daily limit

    withdrawal

    right PIN

    enough money

    NOT above limit

    CASH

    this means that

    src.atm.withdraw(
        right_pin=True,
        enough_money=True,
    )
    

    is the same as

    src.atm.withdraw(
        right_pin=True,
        enough_money=True,
        above_daily_limit=False,
    )
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I change the name of the test from test_right_pin_enough_money to test_right_pin_enough_money_w_limit

     5class TestATM(unittest.TestCase):
     6
     7    def test_right_pin_enough_money_w_limit(self):
     8        reality = src.atm.withdraw(
     9            right_pin=True,
    10            enough_money=True,
    11            above_daily_limit=True,
    12        )
    13        self.assertEqual(reality, DENIED)
    

test_right_pin_not_enough_money_w_limit

The truth table for if the right PIN is entered and there is NOT enough money in the account, is

PIN

money

daily limit

withdrawal

right PIN

NOT enough money

above limit

DENIED

right PIN

NOT enough money

NOT above limit

DENIED

  • I add a value for the above_daily_limit parameter to the assertion in test_right_pin_not_enough_money, for the case where the right PIN is entered, there is NOT enough money in the account, and it is above limit for daily withdrawals,

    PIN

    money

    daily limit

    withdrawal

    right PIN

    NOT enough money

    above limit

    DENIED

    25    def test_right_pin_not_enough_money(self):
    26        reality = src.atm.withdraw(
    27            right_pin=True,
    28            enough_money=False,
    29            above_daily_limit=True,
    30        )
    31        self.assertEqual(reality, DENIED)
    32
    33    def test_wrong_pin_enough_money(self):
    

    the test is still green.

  • I add an assertion for when the right PIN is entered, there is NOT enough money in the account, and it is NOT above limit for daily withdrawals

    PIN

    money

    daily limit

    withdrawal

    right PIN

    NOT enough money

    above limit

    DENIED

    25def test_right_pin_not_enough_money(self):
    26    reality = src.atm.withdraw(
    27        right_pin=True,
    28        enough_money=False,
    29        above_daily_limit=True,
    30    )
    31    self.assertEqual(reality, DENIED)
    32
    33    reality = src.atm.withdraw(
    34        right_pin=True,
    35        enough_money=False,
    36    )
    37    self.assertEqual(reality, DENIED)
    38
    39def test_wrong_pin_enough_money(self):
    

    still green. I do not need to give a value for the above_daily_limit parameter because the default value for the above_daily_limit parameter of the withdraw function is False. This means that

    src.atm.withdraw(
        right_pin=True,
        enough_money=False,
    )
    

    is the same as

    src.atm.withdraw(
        right_pin=True,
        enough_money=False,
        above_daily_limit=False,
    )
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I change the name of the test from test_right_pin_not_enough_money to test_right_pin_not_enough_money_w_limit

    18        my_expectation = 'CASH'
    19        reality = src.atm.withdraw(
    20            right_pin=True,
    21            enough_money=True,
    22        )
    23        self.assertEqual(reality, my_expectation)
    24
    25    def test_right_pin_not_enough_money_w_limit(self):
    26        reality = src.atm.withdraw(
    27            right_pin=True,
    28            enough_money=False,
    29            above_daily_limit=True,
    30        )
    31        self.assertEqual(reality, DENIED)
    

test_wrong_pin_enough_money_w_limit

The truth table for if the wrong PIN is entered and there is enough money in the account, is

PIN

money

daily limit

withdrawal

wrong PIN

enough money

above limit

DENIED

wrong PIN

enough money

NOT above limit

DENIED

  • I add a value for the above_daily_limit parameter to the assertion in test_wrong_pin_enough_money, for the case where the wrong PIN is entered, there is enough money in the account, and it is above limit for daily withdrawals

    PIN

    money

    daily limit

    withdrawal

    wrong PIN

    enough money

    above limit

    DENIED

    39    def test_wrong_pin_enough_money(self):
    40        reality = src.atm.withdraw(
    41            right_pin=False,
    42            enough_money=True,
    43            above_daily_limit=True,
    44        )
    45        self.assertEqual(reality, DENIED)
    46
    47    def test_wrong_pin_not_enough_money(self):
    

    still green.

  • I add an assertion for when the wrong PIN is entered, there is enough money in the account, and it is NOT above limit for daily withdrawals

    PIN

    money

    daily limit

    withdrawal

    wrong PIN

    enough money

    NOT above limit

    DENIED

    39    def test_wrong_pin_enough_money(self):
    40        reality = src.atm.withdraw(
    41            right_pin=False,
    42            enough_money=True,
    43            above_daily_limit=True,
    44        )
    45        self.assertEqual(reality, DENIED)
    46
    47        reality = src.atm.withdraw(
    48            right_pin=False,
    49            enough_money=True,
    50        )
    51        self.assertEqual(reality, DENIED)
    52
    53    def test_wrong_pin_not_enough_money(self):
    

    green. I do not need to give a value for the above_daily_limit parameter in the call to src.atm.withdraw because the default value for the above_daily_limit parameter of the withdraw function is False. This means that

    src.atm.withdraw(
        right_pin=False,
        enough_money=True,
    )
    

    is the same as

    src.atm.withdraw(
        right_pin=False,
        enough_money=True,
        above_daily_limit=False,
    )
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I change the name of the test from test_wrong_pin_enough_money to test_wrong_pin_enough_money_w_limit

    33        reality = src.atm.withdraw(
    34            right_pin=True,
    35            enough_money=False,
    36        )
    37        self.assertEqual(reality, DENIED)
    38
    39    def test_wrong_pin_enough_money_w_limit(self):
    40        reality = src.atm.withdraw(
    41            right_pin=False,
    42            enough_money=True,
    43            above_daily_limit=True,
    44        )
    45        self.assertEqual(reality, DENIED)
    

test_wrong_pin_not_enough_money_w_limit

The truth table for if the wrong PIN is entered and there is NOT enough money in the account, is

PIN

money

daily limit

withdrawal

wrong PIN

NOT enough money

above limit

DENIED

wrong PIN

NOT enough money

NOT above limit

DENIED

  • I add above_daily_limit to the call to src.atm.withdraw in test_wrong_pin_not_enough_money, for when the wrong PIN is entered, there is NOT enough money in the account, and it is above limit for daily withdrawals

    PIN

    money

    daily limit

    withdrawal

    wrong PIN

    NOT enough money

    above limit

    DENIED

    53    def test_wrong_pin_not_enough_money(self):
    54        reality = src.atm.withdraw(
    55            right_pin=False,
    56            enough_money=False,
    57            above_daily_limit=True,
    58        )
    59        self.assertEqual(reality, DENIED)
    60
    61
    62# Exceptions seen
    

    still green.

  • I add an assertion for when the wrong PIN is entered, there is NOT enough money in the account, and it is NOT above limit for daily withdrawals

    PIN

    money

    daily limit

    withdrawal

    wrong PIN

    NOT enough money

    NOT above limit

    DENIED

    53    def test_wrong_pin_not_enough_money(self):
    54        reality = src.atm.withdraw(
    55            right_pin=False,
    56            enough_money=False,
    57            above_daily_limit=True,
    58        )
    59        self.assertEqual(reality, DENIED)
    60
    61        reality = src.atm.withdraw(
    62            right_pin=False,
    63            enough_money=False,
    64        )
    65        self.assertEqual(reality, DENIED)
    66
    67
    68# Exceptions seen
    

    the test is still green. I do not need to give a value for the above_daily_limit parameter in the call to src.atm.withdraw because the default value for the above_daily_limit parameter of the withdraw function is False. This means that

    reality = src.atm.withdraw(
        right_pin=False,
        enough_money=False,
    )
    

    is the same as

    reality = src.atm.withdraw(
        right_pin=False,
        enough_money=False,
        above_daily_limit=False,
    )
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I change the name of the test from test_wrong_pin_not_enough_money to test_wrong_pin_not_enough_money_w_limit

    47        reality = src.atm.withdraw(
    48            right_pin=False,
    49            enough_money=True,
    50        )
    51        self.assertEqual(reality, DENIED)
    52
    53    def test_wrong_pin_not_enough_money_w_limit(self):
    54        reality = src.atm.withdraw(
    55            right_pin=False,
    56            enough_money=False,
    57            above_daily_limit=True,
    58        )
    59        self.assertEqual(reality, DENIED)
    
  • I call the withdraw function directly in test_wrong_pin_not_enough_money_w_limit. I do not need the reality variable as a middle man because I only use the variables once for each assertion

    53    def test_wrong_pin_not_enough_money_w_limit(self):
    54        reality = src.atm.withdraw(
    55            right_pin=False,
    56            enough_money=False,
    57            above_daily_limit=True,
    58        )
    59        # self.assertEqual(reality, DENIED)
    60        self.assertEqual(
    61            src.atm.withdraw(
    62                right_pin=False,
    63                enough_money=False,
    64                above_daily_limit=True,
    65            ),
    66            DENIED
    67        )
    68
    69        reality = src.atm.withdraw(
    70            right_pin=False,
    71            enough_money=False,
    72        )
    73        # self.assertEqual(reality, DENIED)
    74        self.assertEqual(
    75            src.atm.withdraw(
    76                right_pin=False,
    77                enough_money=False,
    78            ),
    79            DENIED
    80        )
    

    still green.

  • I remove the commented lines and unused variables from test_wrong_pin_not_enough_money_w_limit

    53    def test_wrong_pin_not_enough_money_w_limit(self):
    54        self.assertEqual(
    55            src.atm.withdraw(
    56                right_pin=False,
    57                enough_money=False,
    58                above_daily_limit=True,
    59            ),
    60            DENIED
    61        )
    62
    63        self.assertEqual(
    64            src.atm.withdraw(
    65                right_pin=False,
    66                enough_money=False,
    67            ),
    68            DENIED
    69        )
    70
    71
    72# Exceptions seen
    
  • I make the same change in test_wrong_pin_enough_money_w_limit

    39    def test_wrong_pin_enough_money_w_limit(self):
    40        reality = src.atm.withdraw(
    41            right_pin=False,
    42            enough_money=True,
    43            above_daily_limit=True,
    44        )
    45        # self.assertEqual(reality, DENIED)
    46        self.assertEqual(
    47            src.atm.withdraw(
    48                right_pin=False,
    49                enough_money=True,
    50                above_daily_limit=True,
    51            ),
    52            DENIED
    53        )
    54
    55        reality = src.atm.withdraw(
    56            right_pin=False,
    57            enough_money=True,
    58        )
    59        # self.assertEqual(reality, DENIED)
    60        self.assertEqual(
    61            src.atm.withdraw(
    62                right_pin=False,
    63                enough_money=True,
    64            ),
    65            DENIED
    66        )
    67
    68    def test_wrong_pin_not_enough_money_w_limit(self):
    

    green.

  • I remove the commented lines and unused variables from test_wrong_pin_enough_money_w_limit

    39    def test_wrong_pin_enough_money_w_limit(self):
    40        self.assertEqual(
    41            src.atm.withdraw(
    42                right_pin=False,
    43                enough_money=True,
    44                above_daily_limit=True,
    45            ),
    46            DENIED
    47        )
    48
    49        self.assertEqual(
    50            src.atm.withdraw(
    51                right_pin=False,
    52                enough_money=True,
    53            ),
    54            DENIED
    55        )
    56
    57    def test_wrong_pin_not_enough_money_w_limit(self):
    
  • I change test_right_pin_not_enough_money_w_limit

    25    def test_right_pin_not_enough_money_w_limit(self):
    26        self.assertEqual(
    27            src.atm.withdraw(
    28                right_pin=True,
    29                enough_money=False,
    30                above_daily_limit=True,
    31            ),
    32            DENIED
    33        )
    34
    35        self.assertEqual(
    36            src.atm.withdraw(
    37                right_pin=True,
    38                enough_money=False,
    39            ),
    40            DENIED
    41        )
    42
    43    def test_wrong_pin_enough_money_w_limit(self):
    

    still green.

  • I also change test_right_pin_enough_money_w_limit

    10    def test_right_pin_enough_money_w_limit(self):
    11        self.assertEqual(
    12            src.atm.withdraw(
    13                right_pin=True,
    14                enough_money=True,
    15                above_daily_limit=True,
    16            ),
    17            DENIED
    18        )
    19
    20        self.assertEqual(
    21            src.atm.withdraw(
    22                right_pin=True,
    23                enough_money=True,
    24            ),
    25            'CASH'
    26        )
    27
    28    def test_right_pin_not_enough_money_w_limit(self):
    

    the test is still green.


test_right_pin_enough_money_w_card

The truth table for the Automated Teller Machine is now

PIN

money

daily limit

withdrawal

right PIN

enough money

above limit

DENIED

right PIN

enough money

NOT above limit

CASH

right PIN

NOT enough money

above limit

DENIED

right PIN

NOT enough money

NOT above limit

DENIED

PIN

money

daily limit

withdrawal

wrong PIN

enough money

above limit

DENIED

wrong PIN

enough money

NOT above limit

DENIED

wrong PIN

NOT enough money

above limit

DENIED

wrong PIN

NOT enough money

NOT above limit

DENIED

What if the bank card has expired? The inputs for the ATM will then be

  • has the card expired?

  • is the PIN correct?

  • is the amount I want to take, smaller or bigger than what is in the account?

  • will the withdrawal put the account above or below the daily limit for withdrawals?

The truth table for if the right PIN is entered, AND there is enough money in the account, is

PIN

money

daily limit

card expired

withdrawal

right PIN

enough money

above limit

expired

DENIED

right PIN

enough money

above limit

NOT expired

DENIED

right PIN

enough money

NOT above limit

expired

DENIED

right PIN

enough money

NOT above limit

NOT expired

CASH


RED: make it fail


I add a value for the card_expired parameter to the call to the withdraw function for the case where the right PIN is entered, there is enough money in the account, it is above limit for daily withdrawals, and the card has expired, in the first assertion in test_right_pin_enough_money_w_limit

PIN

money

daily limit

card expired

withdrawal

right PIN

enough money

above limit

expired

DENIED

 7    def test_right_pin_enough_money_w_limit(self):
 8        self.assertEqual(
 9            src.atm.withdraw(
10                right_pin=True,
11                enough_money=True,
12                above_daily_limit=True,
13                card_expired=True,
14            ),
15            DENIED
16        )

the terminal is my friend, and shows TypeError

TypeError: withdraw() got an unexpected keyword argument 'card_expired'

because the withdraw function only takes 2 required arguments (right_pin, enough_money) and 1 optional argument (above_daily_limit) and the test called it with 4 arguments (right_pin, enough_money, above_daily_limit and card_expired)


GREEN: make it pass



REFACTOR: make it better


  • I add an assertion for when the right PIN is entered, there is enough money in the account, it is above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    enough money

    above limit

    NOT expired

    DENIED

    10    def test_right_pin_enough_money_w_limit(self):
    11        self.assertEqual(
    12            src.atm.withdraw(
    13                right_pin=True,
    14                enough_money=True,
    15                above_daily_limit=True,
    16                card_expired=True,
    17            ),
    18            DENIED
    19        )
    20
    21        self.assertEqual(
    22            src.atm.withdraw(
    23                right_pin=True,
    24                enough_money=True,
    25                above_daily_limit=True,
    26                card_expired=False,
    27            ),
    28            DENIED
    29        )
    30
    31        self.assertEqual(
    32            src.atm.withdraw(
    33                right_pin=True,
    34                enough_money=True,
    35            ),
    36            'CASH'
    37        )
    

    the test is still green. I add a value for the card_expired parameter to make it clearer, even though it is the default value

  • I add an assertion for when the right PIN is entered, there is enough money in the account, it is NOT above limit for daily withdrawals, and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    enough money

    NOT above limit

    expired

    DENIED

    10    def test_right_pin_enough_money_w_limit(self):
    11        self.assertEqual(
    12            src.atm.withdraw(
    13                right_pin=True,
    14                enough_money=True,
    15                above_daily_limit=True,
    16                card_expired=True,
    17            ),
    18            DENIED
    19        )
    20
    21        self.assertEqual(
    22            src.atm.withdraw(
    23                right_pin=True,
    24                enough_money=True,
    25                above_daily_limit=True,
    26                card_expired=False,
    27            ),
    28            DENIED
    29        )
    30
    31        self.assertEqual(
    32            src.atm.withdraw(
    33                right_pin=True,
    34                enough_money=True,
    35                above_daily_limit=False,
    36                card_expired=True,
    37            ),
    38            DENIED
    39        )
    40
    41        self.assertEqual(
    42            src.atm.withdraw(
    43                right_pin=True,
    44                enough_money=True,
    45            ),
    46            'CASH'
    47        )
    48
    49    def test_right_pin_not_enough_money_w_limit(self):
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'CASH' != 'DENIED'
    

    because the withdraw function gives me ‘CASH’ and the assertion expects ‘DENIED’

  • I add an if statement to the withdraw function in atm.py

     1def withdraw(
     2        right_pin, enough_money,
     3        above_daily_limit=False, card_expired=False,
     4    ):
     5    denied = 'DENIED'
     6
     7    if card_expired == True:
     8        return denied
     9
    10    if above_daily_limit:
    11        return denied
    12
    13    if not right_pin:
    14        return denied
    15
    16    if not enough_money:
    17        return denied
    18
    19    return 'CASH'
    

    the test passes.

  • I use the bool built-in function

    7    # if card_expired == True:
    8    if bool(card_expired) == True:
    9        return denied
    

    the test is still green.

  • I remove == True

     7    # if card_expired == True:
     8    # if bool(card_expired) == True:
     9    if bool(card_expired):
    10        return denied
    

    still green.

  • I remove bool

     7    # if card_expired == True:
     8    # if bool(card_expired) == True:
     9    # if bool(card_expired):
    10    if card_expired:
    11        return denied
    

    green, because if bool(something) == True is the same as if bool(something) is the same as if something

  • I remove the commented lines

     1def withdraw(
     2        right_pin, enough_money,
     3        above_daily_limit=False, card_expired=False,
     4    ):
     5    denied = 'DENIED'
     6
     7    if card_expired:
     8        return denied
     9
    10    if above_daily_limit:
    11        return denied
    12
    13    if not right_pin:
    14        return denied
    15
    16    if not enough_money:
    17        return denied
    18
    19    return 'CASH'
    

    this is what happens when the withdraw function is called

    • it returns ‘DENIED’ if the card has expired

    • it returns ‘DENIED’ if the account is above limit for daily withdrawals

    • it returns ‘DENIED’ if the wrong PIN is entered

    • it returns ‘DENIED’ if there is NOT enough money in the account

    • it gives me ‘CASH’ if the above conditions are NOT met

  • I add values for the above_daily_limit and card_expired parameters to make it clearer even though they have default values, in the next assertion in test_right_pin_enough_money_w_limit for the case where the right PIN is entered, there is enough money in the account, it is NOT above limit for daily withdrawals, and the card has NOT expired, in test_atm.py

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    enough money

    NOT above limit

    NOT expired

    CASH

    10    def test_right_pin_enough_money_w_limit(self):
    11        self.assertEqual(
    12            src.atm.withdraw(
    13                right_pin=True,
    14                enough_money=True,
    15                above_daily_limit=True,
    16                card_expired=True,
    17            ),
    18            DENIED
    19        )
    20
    21        self.assertEqual(
    22            src.atm.withdraw(
    23                right_pin=True,
    24                enough_money=True,
    25                above_daily_limit=True,
    26                card_expired=False,
    27            ),
    28            DENIED
    29        )
    30
    31        self.assertEqual(
    32            src.atm.withdraw(
    33                right_pin=True,
    34                enough_money=True,
    35                above_daily_limit=False,
    36                card_expired=True,
    37            ),
    38            DENIED
    39        )
    40
    41        self.assertEqual(
    42            src.atm.withdraw(
    43                right_pin=True,
    44                enough_money=True,
    45                above_daily_limit=False,
    46                card_expired=False,
    47            ),
    48            'CASH'
    49        )
    50
    51    def test_right_pin_not_enough_money_w_limit(self):
    

    still green.

  • I change the name of the test from test_right_pin_enough_money_w_limit to test_right_pin_enough_money_w_card

     8class TestATM(unittest.TestCase):
     9
    10    def test_right_pin_enough_money_w_card(self):
    11        self.assertEqual(
    12            src.atm.withdraw(
    13                right_pin=True,
    14                enough_money=True,
    15                above_daily_limit=True,
    16                card_expired=True,
    17            ),
    18            DENIED
    19        )
    

test_right_pin_not_enough_money_w_card

The truth table for if the right PIN is entered AND there is NOT enough money in the account, is

PIN

money

daily limit

card expired

withdrawal

right PIN

NOT enough money

above limit

expired

DENIED

right PIN

NOT enough money

above limit

NOT expired

DENIED

right PIN

NOT enough money

NOT above limit

expired

DENIED

right PIN

NOT enough money

NOT above limit

NOT expired

DENIED

  • I add a value for the card_expired parameter to the first assertion in test_right_pin_not_enough_money_w_limit for when the right PIN is entered, there is NOT enough money in the account, it is above limit for daily withdrawals, and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    NOT enough money

    above limit

    expired

    DENIED

    51    def test_right_pin_not_enough_money_w_limit(self):
    52        self.assertEqual(
    53            src.atm.withdraw(
    54                right_pin=True,
    55                enough_money=False,
    56                above_daily_limit=True,
    57                card_expired=True,
    58            ),
    59            DENIED
    60        )
    

    the test is still green.

  • I add an assertion for when the right PIN is entered, there is NOT enough money in the account, it is above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    NOT enough money

    above limit

    NOT expired

    DENIED

    51    def test_right_pin_not_enough_money_w_limit(self):
    52        self.assertEqual(
    53            src.atm.withdraw(
    54                right_pin=True,
    55                enough_money=False,
    56                above_daily_limit=True,
    57                card_expired=True,
    58            ),
    59            DENIED
    60        )
    61
    62        self.assertEqual(
    63            src.atm.withdraw(
    64                right_pin=True,
    65                enough_money=False,
    66                above_daily_limit=True,
    67                card_expired=False,
    68            ),
    69            DENIED
    70        )
    71
    72        self.assertEqual(
    73            src.atm.withdraw(
    74                right_pin=True,
    75                enough_money=False,
    76            ),
    77            DENIED
    78        )
    79
    80    def test_wrong_pin_enough_money_w_limit(self):
    

    still green.

  • I add an assertion for when the right PIN is entered, there is NOT enough money in the account, it is NOT above limit for daily withdrawals, and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    NOT enough money

    NOT above limit

    expired

    DENIED

    51    def test_right_pin_not_enough_money_w_limit(self):
    52        self.assertEqual(
    53            src.atm.withdraw(
    54                right_pin=True,
    55                enough_money=False,
    56                above_daily_limit=True,
    57                card_expired=True,
    58            ),
    59            DENIED
    60        )
    61
    62        self.assertEqual(
    63            src.atm.withdraw(
    64                right_pin=True,
    65                enough_money=False,
    66                above_daily_limit=True,
    67                card_expired=False,
    68            ),
    69            DENIED
    70        )
    71
    72        self.assertEqual(
    73            src.atm.withdraw(
    74                right_pin=True,
    75                enough_money=False,
    76                above_daily_limit=False,
    77                card_expired=True,
    78            ),
    79            DENIED
    80        )
    81
    82        self.assertEqual(
    83            src.atm.withdraw(
    84                right_pin=True,
    85                enough_money=False,
    86            ),
    87            DENIED
    88        )
    89
    90    def test_wrong_pin_enough_money_w_limit(self):
    

    green.

  • I add values for the above_daily_limit and card_expired parameters to the assertion for when the right PIN is entered, there is NOT enough money in the account, it is NOT above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    right PIN

    NOT enough money

    NOT above limit

    NOT expired

    DENIED

     7    def test_right_pin_not_enough_money_w_limit(self):
     8        self.assertEqual(
     9            src.atm.withdraw(
    10                right_pin=True,
    11                enough_money=False,
    12                above_daily_limit=True,
    13                card_expired=True,
    14            ),
    15            DENIED
    16        )
    17
    18        self.assertEqual(
    19            src.atm.withdraw(
    20                right_pin=True,
    21                enough_money=False,
    22                above_daily_limit=True,
    23                card_expired=False,
    24            ),
    25            DENIED
    26        )
    27
    28        self.assertEqual(
    29            src.atm.withdraw(
    30                right_pin=True,
    31                enough_money=False,
    32                above_daily_limit=False,
    33                card_expired=True,
    34            ),
    35            DENIED
    36        )
    37
    38        self.assertEqual(
    39            src.atm.withdraw(
    40                right_pin=True,
    41                enough_money=False,
    42                above_daily_limit=False,
    43                card_expired=True,
    44            ),
    45            DENIED
    46        )
    47
    48    def test_wrong_pin_enough_money_w_limit(self):
    

    still green.

  • I change the name of the test from test_right_pin_not_enough_money_w_limit to test_right_pin_not_enough_money_w_card

    41        self.assertEqual(
    42            src.atm.withdraw(
    43                right_pin=True,
    44                enough_money=True,
    45                above_daily_limit=False,
    46                card_expired=False,
    47            ),
    48            'CASH'
    49        )
    50
    51    def test_right_pin_not_enough_money_w_card(self):
    52        self.assertEqual(
    53            src.atm.withdraw(
    54                right_pin=True,
    55                enough_money=False,
    56                above_daily_limit=True,
    57                card_expired=True,
    58            ),
    59            DENIED
    60        )
    

test_wrong_pin_enough_money_w_card

The truth table for if the wrong PIN is entered, and there is enough money in the account, is

PIN

money

daily limit

card expired

withdrawal

wrong PIN

enough money

above limit

expired

DENIED

wrong PIN

enough money

above limit

NOT expired

DENIED

wrong PIN

enough money

NOT above limit

expired

DENIED

wrong PIN

enough money

NOT above limit

NOT expired

DENIED

  • I add a value for the card_expired parameter to the assertion in test_wrong_pin_enough_money_w_limit for the case where the wrong PIN is entered, there is enough money in the account, it is above limit for daily withdrawals, and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    enough money

    above limit

    expired

    DENIED

     92    def test_wrong_pin_enough_money_w_limit(self):
     93        self.assertEqual(
     94            src.atm.withdraw(
     95                right_pin=False,
     96                enough_money=True,
     97                above_daily_limit=True,
     98                card_expired=True,
     99            ),
    100            DENIED
    101        )
    

    the test is still green.

  • I add an assertion for the case where the wrong PIN is entered, there is enough money in the account, it is above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    enough money

    above limit

    NOT expired

    DENIED

     92    def test_wrong_pin_enough_money_w_limit(self):
     93        self.assertEqual(
     94            src.atm.withdraw(
     95                right_pin=False,
     96                enough_money=True,
     97                above_daily_limit=True,
     98                card_expired=True,
     99            ),
    100            DENIED
    101        )
    102
    103        self.assertEqual(
    104            src.atm.withdraw(
    105                right_pin=False,
    106                enough_money=True,
    107                above_daily_limit=True,
    108                card_expired=False,
    109            ),
    110            DENIED
    111        )
    112
    113        self.assertEqual(
    114            src.atm.withdraw(
    115                right_pin=False,
    116                enough_money=True,
    117            ),
    118            DENIED
    119        )
    120
    121    def test_wrong_pin_not_enough_money_w_limit(self):
    

    still green.

  • I add an assertion for when the wrong PIN is entered, there is enough money in the account, it is NOT above limit for daily withdrawals, and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    enough money

    NOT above limit

    expired

    DENIED

     92    def test_wrong_pin_enough_money_w_limit(self):
     93        self.assertEqual(
     94            src.atm.withdraw(
     95                right_pin=False,
     96                enough_money=True,
     97                above_daily_limit=True,
     98                card_expired=True,
     99            ),
    100            DENIED
    101        )
    102
    103        self.assertEqual(
    104            src.atm.withdraw(
    105                right_pin=False,
    106                enough_money=True,
    107                above_daily_limit=True,
    108                card_expired=False,
    109            ),
    110            DENIED
    111        )
    112
    113        self.assertEqual(
    114            src.atm.withdraw(
    115                right_pin=False,
    116                enough_money=True,
    117                above_daily_limit=False,
    118                card_expired=True,
    119            ),
    120            DENIED
    121        )
    122
    123        self.assertEqual(
    124            src.atm.withdraw(
    125                right_pin=False,
    126                enough_money=True,
    127            ),
    128            DENIED
    129        )
    130
    131    def test_wrong_pin_not_enough_money_w_limit(self):
    

    still green.

  • I add values for the card_expired and above_daily_limit parameters to the last assertion to make it clearer, for when the wrong PIN is entered, there is enough money in the account, it is NOT above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    enough money

    NOT above limit

    NOT expired

    DENIED

     92    def test_wrong_pin_enough_money_w_limit(self):
     93        self.assertEqual(
     94            src.atm.withdraw(
     95                right_pin=False,
     96                enough_money=True,
     97                above_daily_limit=True,
     98                card_expired=True,
     99            ),
    100            DENIED
    101        )
    102
    103        self.assertEqual(
    104            src.atm.withdraw(
    105                right_pin=False,
    106                enough_money=True,
    107                above_daily_limit=True,
    108                card_expired=False,
    109            ),
    110            DENIED
    111        )
    112
    113        self.assertEqual(
    114            src.atm.withdraw(
    115                right_pin=False,
    116                enough_money=True,
    117                above_daily_limit=False,
    118                card_expired=True,
    119            ),
    120            DENIED
    121        )
    122
    123        self.assertEqual(
    124            src.atm.withdraw(
    125                right_pin=False,
    126                enough_money=True,
    127                above_daily_limit=False,
    128                card_expired=False,
    129            ),
    130            DENIED
    131        )
    132
    133    def test_wrong_pin_not_enough_money_w_limit(self):
    

    the test is still green.

  • I change the name of test_wrong_pin_enough_money_w_limit to test_wrong_pin_enough_money_w_card

     82        self.assertEqual(
     83            src.atm.withdraw(
     84                right_pin=True,
     85                enough_money=False,
     86                above_daily_limit=False,
     87                card_expired=True,
     88            ),
     89            DENIED
     90        )
     91
     92    def test_wrong_pin_enough_money_w_card(self):
     93        self.assertEqual(
     94            src.atm.withdraw(
     95                right_pin=False,
     96                enough_money=True,
     97                above_daily_limit=True,
     98                card_expired=True,
     99            ),
    100            DENIED
    101        )
    

test_wrong_pin_not_enough_money_w_card

The truth table for if the wrong PIN is entered, and there is NOT enough money in the account, is

PIN

money

daily limit

card expired

withdrawal

wrong PIN

NOT enough money

above limit

expired

DENIED

wrong PIN

NOT enough money

above limit

NOT expired

DENIED

wrong PIN

NOT enough money

NOT above limit

expired

DENIED

wrong PIN

NOT enough money

NOT above limit

NOT expired

DENIED

  • I add a value for the card_expired parameter to the first assertion in test_wrong_pin_not_enough_money_w_limit for when the wrong PIN is entered, there is NOT enough money in the account, it is above limit for daily withdrawals, and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    NOT enough money

    above limit

    expired

    DENIED

    133    def test_wrong_pin_not_enough_money_w_limit(self):
    134        self.assertEqual(
    135            src.atm.withdraw(
    136                right_pin=False,
    137                enough_money=False,
    138                above_daily_limit=True,
    139                card_expired=True,
    140            ),
    141            DENIED
    142        )
    

    still green.

  • I add an assertion, for when the wrong PIN is entered, there is NOT enough money in the account, it is above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    NOT enough money

    above limit

    NOT expired

    DENIED

    113    def test_wrong_pin_not_enough_money_w_limit(self):
    114        self.assertEqual(
    115            src.atm.withdraw(
    116                right_pin=False,
    117                enough_money=False,
    118                above_daily_limit=True,
    119                card_expired=True,
    120            ),
    121            DENIED
    122        )
    123
    124        self.assertEqual(
    125            src.atm.withdraw(
    126                right_pin=False,
    127                enough_money=False,
    128                above_daily_limit=True,
    129                card_expired=False,
    130            ),
    131            DENIED
    132        )
    133
    134        self.assertEqual(
    135            src.atm.withdraw(
    136                right_pin=False,
    137                enough_money=False,
    138            ),
    139            DENIED
    140        )
    141
    142
    143# Exceptions seen
    

    green.

  • I add an assertion for when the wrong PIN is entered, there is NOT enough money in the account, it is NOT above limit for daily withdrawals and the card has expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    NOT enough money

    NOT above limit

    expired

    DENIED

    133    def test_wrong_pin_not_enough_money_w_limit(self):
    134        self.assertEqual(
    135            src.atm.withdraw(
    136                right_pin=False,
    137                enough_money=False,
    138                above_daily_limit=True,
    139                card_expired=True,
    140            ),
    141            DENIED
    142        )
    143
    144        self.assertEqual(
    145            src.atm.withdraw(
    146                right_pin=False,
    147                enough_money=False,
    148                above_daily_limit=True,
    149                card_expired=False,
    150            ),
    151            DENIED
    152        )
    153
    154        self.assertEqual(
    155            src.atm.withdraw(
    156                right_pin=False,
    157                enough_money=False,
    158                above_daily_limit=False,
    159                card_expired=True,
    160            ),
    161            DENIED
    162        )
    163
    164        self.assertEqual(
    165            src.atm.withdraw(
    166                right_pin=False,
    167                enough_money=False,
    168            ),
    169            DENIED
    170        )
    171
    172
    173# Exceptions seen
    

    still green.

  • I add values for the above_daily_limit and card_expired parameters to make it clearer, for when the wrong PIN is entered, there is NOT enough money in the account, it is NOT above limit for daily withdrawals, and the card has NOT expired

    PIN

    money

    daily limit

    card expired

    withdrawal

    wrong PIN

    NOT enough money

    NOT above limit

    NOT expired

    DENIED

    133    def test_wrong_pin_not_enough_money_w_limit(self):
    134        self.assertEqual(
    135            src.atm.withdraw(
    136                right_pin=False,
    137                enough_money=False,
    138                above_daily_limit=True,
    139                card_expired=True,
    140            ),
    141            DENIED
    142        )
    143
    144        self.assertEqual(
    145            src.atm.withdraw(
    146                right_pin=False,
    147                enough_money=False,
    148                above_daily_limit=True,
    149                card_expired=False,
    150            ),
    151            DENIED
    152        )
    153
    154        self.assertEqual(
    155            src.atm.withdraw(
    156                right_pin=False,
    157                enough_money=False,
    158                above_daily_limit=False,
    159                card_expired=True,
    160            ),
    161            DENIED
    162        )
    163
    164        self.assertEqual(
    165            src.atm.withdraw(
    166                right_pin=False,
    167                enough_money=False,
    168                above_daily_limit=False,
    169                card_expired=False,
    170            ),
    171            DENIED
    172        )
    173
    174
    175# Exceptions seen
    

    all the tests are green.

The withdraw function can be written with Logical Disjunction (OR) or Logical Conjunction (AND). Try it and see which one you like better?

close the project

  • I close test_atm.py and atm.py in the editor

  • I click in the terminal where the tests are running, then use q on the keyboard to leave the tests. The terminal goes back to the command line.

  • I change directory to the parent of atm

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

I ran tests for an Automated Teller Machine with these inputs:

  • has the card expired?

  • is the PIN correct?

  • is the amount I want to take, smaller or bigger than what is in the account?

  • will the withdrawal put the account above or below the daily limit for withdrawals?

the inputs gave me this truth table

PIN

money

daily limit

card expired

withdrawal

right PIN

enough money

above limit

expired

DENIED

right PIN

enough money

above limit

NOT expired

DENIED

right PIN

enough money

NOT above limit

expired

DENIED

right PIN

enough money

NOT above limit

NOT expired

CASH

PIN

money

daily limit

card expired

withdrawal

right PIN

NOT enough money

above limit

expired

DENIED

right PIN

NOT enough money

above limit

NOT expired

DENIED

right PIN

NOT enough money

NOT above limit

expired

DENIED

right PIN

NOT enough money

NOT above limit

NOT expired

DENIED

PIN

money

daily limit

card expired

withdrawal

wrong PIN

enough money

above limit

expired

DENIED

wrong PIN

enough money

above limit

NOT expired

DENIED

wrong PIN

enough money

NOT above limit

expired

DENIED

wrong PIN

enough money

NOT above limit

NOT expired

DENIED

PIN

money

daily limit

card expired

withdrawal

wrong PIN

NOT enough money

above limit

expired

DENIED

wrong PIN

NOT enough money

above limit

NOT expired

DENIED

wrong PIN

NOT enough money

NOT above limit

expired

DENIED

wrong PIN

NOT enough money

NOT above limit

NOT expired

DENIED

The ATM only gives me 'CASH' when the right PIN is entered, there is enough money in the account, it is NOT above limit for daily withdrawals, and the bank card has NOT expired

What if I want the ATM to give a different message with each denial, so that the user knows why the withdrawal failed? The truth table could then be

PIN

money

daily limit

card expired

withdrawal

right PIN

enough money

above limit

expired

DENIED: Card Expired

right PIN

enough money

above limit

NOT expired

DENIED: You have exceeded the daily withdrawal limit

right PIN

enough money

NOT above limit

expired

DENIED: Card Expired

right PIN

enough money

NOT above limit

NOT expired

CASH

PIN

money

daily limit

card expired

withdrawal

right PIN

NOT enough money

above limit

expired

DENIED: Card Expired

right PIN

NOT enough money

above limit

NOT expired

DENIED: Insufficient Funds

right PIN

NOT enough money

NOT above limit

expired

DENIED: Card Expired

right PIN

NOT enough money

NOT above limit

NOT expired

DENIED: Insufficient Funds

PIN

money

daily limit

card expired

withdrawal

wrong PIN

enough money

above limit

expired

DENIED: You entered the wrong PIN. Try again…

wrong PIN

enough money

above limit

NOT expired

DENIED: You entered the wrong PIN. Try again…

wrong PIN

enough money

NOT above limit

expired

DENIED: You entered the wrong PIN. Try again…

wrong PIN

enough money

NOT above limit

NOT expired

DENIED: You entered the wrong PIN. Try again…

PIN

money

daily limit

card expired

withdrawal

wrong PIN

NOT enough money

above limit

expired

DENIED: You entered the wrong PIN. Try again…

wrong PIN

NOT enough money

above limit

NOT expired

DENIED: You entered the wrong PIN. Try again…

wrong PIN

NOT enough money

NOT above limit

expired

DENIED: You entered the wrong PIN. Try again…

wrong PIN

NOT enough money

NOT above limit

NOT expired

DENIED: You entered the wrong PIN. Try again…

What would I change in the tests and solution?


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

you know

Would you like to test making a Microwave?


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.