how to make a calculator 7

I want to test the calculator with a dictionary


open the project

  • I change directory to the calculator folder

    cd calculator
    

    the terminal shows I am in the calculator folder

    .../pumping_python/calculator
    
  • I use pytest-watcher to run the tests

    uv run pytest-watcher . --now
    

    the terminal shows

    rootdir: .../pumping_python/calculator
    configfile: pyproject.toml
    collected 7 items
    
    tests/test_calculator.py .......                              [100%]
    
    ======================== 7 passed in X.YZs =========================
    
  • I hold ctrl on the keyboard, then click on tests/test_calculator.py to open it in the editor


test_calculator_w_dictionary_items


RED: make it fail


I add a new test

 58    def test_calculator_w_list_items(self):
 59        two_numbers = [
 60            self.random_first_number,
 61            self.random_second_number
 62        ]
 63
 64        self.assertEqual(
 65            src.calculator.add(
 66                two_numbers[0],
 67                two_numbers[1]
 68            ),
 69            self.random_first_number+self.random_second_number
 70        )
 71        self.assertEqual(
 72            src.calculator.divide(
 73                two_numbers[-2],
 74                two_numbers[-1]
 75            ),
 76            self.random_first_number/self.random_second_number
 77        )
 78        self.assertEqual(
 79            src.calculator.multiply(
 80                two_numbers[1],
 81                two_numbers[-1]
 82            ),
 83            self.random_second_number*self.random_second_number
 84        )
 85        self.assertEqual(
 86            src.calculator.subtract(
 87                two_numbers[-2],
 88                two_numbers[0]
 89            ),
 90            self.random_first_number-self.random_first_number
 91        )
 92        self.assertEqual(
 93            src.calculator.add(*two_numbers),
 94            self.random_first_number+self.random_second_number
 95        )
 96        self.assertEqual(
 97            src.calculator.divide(*two_numbers),
 98            self.random_first_number/self.random_second_number
 99        )
100        self.assertEqual(
101            src.calculator.multiply(*two_numbers),
102            self.random_first_number*self.random_second_number
103        )
104        self.assertEqual(
105            src.calculator.subtract(*two_numbers),
106            self.random_first_number-self.random_second_number
107        )
108
109    def test_calculator_w_dictionary_items(self):
110        two_numbers = {
111            'x': self.random_first_number,
112            'y': self.random_second_number,
113        }
114
115        self.assertEqual(
116            src.calculator.add(
117                two_numbers['x'],
118                two_numbers['y']
119            ),
120            self.random_first_number+self.random_first_number
121        )
122
123    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):

the terminal shows AssertionError

AssertionError: ABC.DEFGHIJKLMNOPQ != RST.UVWXYZABCDEFG

GREEN: make it pass


I change the expectation to the right calculation

115        self.assertEqual(
116            src.calculator.add(
117                two_numbers['x'],
118                two_numbers['y']
119            ),
120            self.random_first_number+self.random_second_number
121        )

the test passes. two_numbers is a dictionary with two key-value pairs

  • 'x': self.random_first_number

  • 'y': self.random_second_number

this means

  • two_numbers['x'] is self.random_first_number

  • two_numbers['y'] is self.random_second_number


REFACTOR: make it better


  • I add an assertion for the divide function

    115        self.assertEqual(
    116            src.calculator.add(
    117                two_numbers['x'],
    118                two_numbers['y']
    119            ),
    120            self.random_first_number+self.random_second_number
    121        )
    122        self.assertEqual(
    123            src.calculator.divide(
    124                two_numbers['x'],
    125                two_numbers['y']
    126            ),
    127            self.random_first_number*self.random_second_number
    128        )
    

    the terminal shows AssertionError

    AssertionError: D.EFGHIJKLMNOPQRST != UVWXY.ZABCDEFGHIJ
    
  • I change the calculation to division

    122        self.assertEqual(
    123            src.calculator.divide(
    124                two_numbers['x'],
    125                two_numbers['y']
    126            ),
    127            self.random_first_number/self.random_second_number
    128        )
    

    the test passes

  • I add an assertion for the multiply function

    122        self.assertEqual(
    123            src.calculator.divide(
    124                two_numbers['x'],
    125                two_numbers['y']
    126            ),
    127            self.random_first_number/self.random_second_number
    128        )
    129        self.assertEqual(
    130            src.calculator.multiply(
    131                two_numbers['y'],
    132                two_numbers['y']
    133            ),
    134            self.random_first_number*self.random_second_number
    135        )
    

    the terminal shows AssertionError

    AssertionError: EFGHIJ.KLMNOPQRSTU != VWXYZ.ABCDEFGHIJKL
    
  • I change the expectation to use the correct variables

    129        self.assertEqual(
    130            src.calculator.multiply(
    131                two_numbers['y'],
    132                two_numbers['y']
    133            ),
    134            self.random_second_number*self.random_second_number
    135        )
    

    the test passes

  • I add an assertion for the subtract function

    129        self.assertEqual(
    130            src.calculator.multiply(
    131                two_numbers['y'],
    132                two_numbers['y']
    133            ),
    134            self.random_second_number*self.random_second_number
    135        )
    136        self.assertEqual(
    137            src.calculator.subtract(
    138                two_numbers['x'],
    139                two_numbers['x'],
    140            ),
    141            self.random_first_number-self.random_second_number
    142        )
    

    the terminal shows AssertionError

    AssertionError: 0.0 != FGH.IJKLMNOPQRSTU
    
  • I change the expectation to match

    109    def test_calculator_w_dictionary_items(self):
    110        two_numbers = {
    111            'first_input': self.random_first_number,
    112            'second_input': self.random_second_number,
    113        }
    114
    115        self.assertEqual(
    116            src.calculator.add(
    117                two_numbers['x'],
    118                two_numbers['y']
    119            ),
    120            self.random_first_number+self.random_second_number
    121        )
    122        self.assertEqual(
    123            src.calculator.divide(
    124                two_numbers['x'],
    125                two_numbers['y']
    126            ),
    127            self.random_first_number/self.random_second_number
    128        )
    129        self.assertEqual(
    130            src.calculator.multiply(
    131                two_numbers['y'],
    132                two_numbers['y']
    133            ),
    134            self.random_second_number*self.random_second_number
    135        )
    136        self.assertEqual(
    137            src.calculator.subtract(
    138                two_numbers['x'],
    139                two_numbers['x'],
    140            ),
    141            self.random_first_number-self.random_first_number
    142        )
    143
    144    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the test passes


test calculator with ** expression


RED: make it fail


I use ** like I did for the keyword arguments in test_functions_w_unknown_arguments

136        self.assertEqual(
137            src.calculator.subtract(
138                two_numbers['x'],
139                two_numbers['x'],
140            ),
141            self.random_first_number-self.random_first_number
142        )
143        self.assertEqual(
144            src.calculator.add(**two_numbers),
145            self.random_first_number-self.random_second_number
146        )

the terminal shows TypeError

TypeError: numbers_only.<locals>.wrapper() got an unexpected keyword argument 'x'

the names of the keys in the two_numbers dictionary must be the same as the names of the arguments the calculator functions receive - first_input and second_input not x and y


GREEN: make it pass


  • I change x and y to first_input and second_input in the dictionary

    109    def test_calculator_w_dictionary_items(self):
    110        two_numbers = {
    111            # 'x': self.random_first_number,
    112            'first_input': self.random_first_number,
    113            # 'y': self.random_second_number,
    114            'second_input': self.random_second_number,
    115        }
    

    the terminal shows KeyError

    KeyError: 'x'
    
  • I add KeyError to the list of Exceptions seen

    214# Exceptions seen
    215# AssertionError
    216# NameError
    217# AttributeError
    218# TypeError
    219# KeyError
    
  • I remove the comments to put x and y back as keys in the dictionary

    109    def test_calculator_w_dictionary_items(self):
    110        two_numbers = {
    111            'x': self.random_first_number,
    112            'first_input': self.random_first_number,
    113            'y': self.random_second_number,
    114            'second_input': self.random_second_number,
    115        }
    116
    117        self.assertEqual(
    118            src.calculator.add(
    119                two_numbers['x'],
    120                two_numbers['y']
    121            ),
    122            self.random_first_number+self.random_second_number
    123        )
    

    the terminal shows TypeError

    TypeError: numbers_only.<locals>.decorator() got an unexpected keyword argument 'x'
    

    the same Exception I had before I added the keys to the dictionary

  • I change two_numbers['x'] to two_numbers['first_input'] to use the new keys in the assertions for addition

    117        self.assertEqual(
    118            src.calculator.add(
    119                # two_numbers['x'],
    120                two_numbers['first_input'],
    121                two_numbers['y']
    122            ),
    123            self.random_first_number+self.random_second_number
    124        )
    
    TypeError: numbers_only.<locals>.decorator() got an unexpected keyword argument 'x'
    

    the same Exception I had before I added the keys to the dictionary

  • I comment out the assertion with **two_numbers

    139        self.assertEqual(
    140            src.calculator.subtract(
    141                two_numbers['x'],
    142                two_numbers['x'],
    143            ),
    144            self.random_first_number-self.random_first_number
    145        )
    146        # self.assertEqual(
    147        #     src.calculator.add(**two_numbers),
    148        #     self.random_first_number-self.random_second_number
    149        # )
    150
    151    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the test is green again

  • I use two_numbers['second_input'] for two_numbers['y']

    117        self.assertEqual(
    118            src.calculator.add(
    119                # two_numbers['x'],
    120                two_numbers['first_input'],
    121                # two_numbers['y']
    122                two_numbers['second_input']
    123            ),
    124            self.random_first_number+self.random_second_number
    125        )
    

    the test is still green

  • I remove the commented lines then make the same change for division

    117        self.assertEqual(
    118            src.calculator.add(
    119                two_numbers['first_input'],
    120                two_numbers['second_input']
    121            ),
    122            self.random_first_number+self.random_second_number
    123        )
    124        self.assertEqual(
    125            src.calculator.divide(
    126                # two_numbers['x'],
    127                two_numbers['first_input'],
    128                # two_numbers['y']
    129                two_numbers['second_input']
    130            ),
    131            self.random_first_number/self.random_second_number
    132        )
    

    still green

  • I do the same thing for the multiply function

    124        self.assertEqual(
    125            src.calculator.divide(
    126                two_numbers['first_input'],
    127                two_numbers['second_input']
    128            ),
    129            self.random_first_number/self.random_second_number
    130        )
    131        self.assertEqual(
    132            src.calculator.multiply(
    133                # two_numbers['y'],
    134                two_numbers['second_input'],
    135                # two_numbers['y']
    136                two_numbers['second_input']
    137            ),
    138            self.random_second_number*self.random_second_number
    139        )
    

    green

  • I do it in the assertions for the subtract function

    131        self.assertEqual(
    132            src.calculator.multiply(
    133                two_numbers['second_input'],
    134                two_numbers['second_input']
    135            ),
    136            self.random_second_number*self.random_second_number
    137        )
    138        self.assertEqual(
    139            src.calculator.subtract(
    140                # two_numbers['x'],
    141                two_numbers['first_input'],
    142                # two_numbers['x'],
    143                two_numbers['first_input']
    144            ),
    145            self.random_first_number-self.random_first_number
    146        )
    

    still green

  • I remove the x and y keys from the dictionary

    109    def test_calculator_w_dictionary_items(self):
    110        two_numbers = {
    111            'first_input': self.random_first_number,
    112            'second_input': self.random_second_number,
    113        }
    114
    115        self.assertEqual(
    116            src.calculator.add(
    117                two_numbers['first_input'],
    118                two_numbers['second_input']
    119            ),
    120            self.random_first_number+self.random_second_number
    121        )
    122        self.assertEqual(
    123            src.calculator.divide(
    124                two_numbers['first_input'],
    125                two_numbers['second_input']
    126            ),
    127            self.random_first_number/self.random_second_number
    128        )
    129        self.assertEqual(
    130            src.calculator.multiply(
    131                two_numbers['second_input'],
    132                two_numbers['second_input']
    133            ),
    134            self.random_second_number*self.random_second_number
    135        )
    136        self.assertEqual(
    137            src.calculator.subtract(
    138                two_numbers['first_input'],
    139                two_numbers['first_input']
    140            ),
    141            self.random_first_number-self.random_first_number
    142        )
    143        # self.assertEqual(
    144        #     src.calculator.add(**two_numbers),
    145        #     self.random_first_number-self.random_second_number
    146        # )
    

    the tests are still green

  • I remove the comments from the assertion with **two_numbers

    143        self.assertEqual(
    144            src.calculator.add(**two_numbers),
    145            self.random_first_number-self.random_second_number
    146        )
    

    the terminal shows AssertionError

    AssertionError: VWX.YZABCDEFGHIJK != LMN.OPQRSTUVWXYZABC
    
  • I change the calculation in the assertion to addition

    143        self.assertEqual(
    144            src.calculator.add(**two_numbers),
    145            self.random_first_number+self.random_second_number
    146        )
    

    the test passes


REFACTOR: make it better


  • I add an assertion for the divide function

    143        self.assertEqual(
    144            src.calculator.add(**two_numbers),
    145            self.random_first_number+self.random_second_number
    146        )
    147        self.assertEqual(
    148            src.calculator.divide(**two_numbers),
    149            self.random_first_number*self.random_second_number
    150        )
    

    the terminal shows AssertionError

    AssertionError: H.IJKLMNOPQRSTUVWX != YZABCD.EFGHIJKLMNO
    
  • I change the calculation to use division

    147        self.assertEqual(
    148            src.calculator.divide(**two_numbers),
    149            self.random_first_number/self.random_second_number
    150        )
    

    the test passes

  • I add an assertion for the multiply function

    147        self.assertEqual(
    148            src.calculator.divide(**two_numbers),
    149            self.random_first_number/self.random_second_number
    150        )
    151        self.assertEqual(
    152            src.calculator.multiply(**two_numbers),
    153            self.random_first_number/self.random_second_number
    154        )
    

    the terminal shows AssertionError

    AssertionError: IJKLMN.OPQRSTUVWX != Y.ZABCDEFGHIJKLMNOP
    
  • I change the calculation to multiplication

    151        self.assertEqual(
    152            src.calculator.multiply(**two_numbers),
    153            self.random_first_number*self.random_second_number
    154        )
    

    the test passes

  • I add the next assertion

    151        self.assertEqual(
    152            src.calculator.multiply(**two_numbers),
    153            self.random_first_number*self.random_second_number
    154        )
    155        self.assertEqual(
    156            src.calculator.subtract(**two_numbers),
    157            self.random_first_number+self.random_second_number
    158        )
    

    the terminal shows AssertionError

    AssertionError: JKL.MNOPQRSTUVWXYZ != ABC.DEFGHIJKLMNOP
    
  • I change the expectation

    109    def test_calculator_w_dictionary_items(self):
    110        two_numbers = {
    111            'first_input': self.random_first_number,
    112            'second_input': self.random_second_number,
    113        }
    114
    115        self.assertEqual(
    116            src.calculator.add(
    117                two_numbers['first_input'],
    118                two_numbers['second_input']
    119            ),
    120            self.random_first_number+self.random_second_number
    121        )
    122        self.assertEqual(
    123            src.calculator.divide(
    124                two_numbers['first_input'],
    125                two_numbers['second_input']
    126            ),
    127            self.random_first_number/self.random_second_number
    128        )
    129        self.assertEqual(
    130            src.calculator.multiply(
    131                two_numbers['second_input'],
    132                two_numbers['second_input']
    133            ),
    134            self.random_second_number*self.random_second_number
    135        )
    136        self.assertEqual(
    137            src.calculator.subtract(
    138                two_numbers['first_input'],
    139                two_numbers['first_input']
    140            ),
    141            self.random_first_number-self.random_first_number
    142        )
    143        self.assertEqual(
    144            src.calculator.add(**two_numbers),
    145            self.random_first_number+self.random_second_number
    146        )
    147        self.assertEqual(
    148            src.calculator.divide(**two_numbers),
    149            self.random_first_number/self.random_second_number
    150        )
    151        self.assertEqual(
    152            src.calculator.multiply(**two_numbers),
    153            self.random_first_number*self.random_second_number
    154        )
    155        self.assertEqual(
    156            src.calculator.subtract(**two_numbers),
    157            self.random_first_number-self.random_second_number
    158        )
    159
    160    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the test passes


test calculator with dictionary values


RED: make it fail


I can use the values method of dictionaries to make a list to test the calculator in test_calculator_w_list_items

58    def test_calculator_w_list_items(self):
59        # two_numbers = [
60        #     self.random_first_number,
61        #     self.random_second_number
62        # ]
63        a_dictionary = {
64            'x': self.random_first_number,
65            'y': self.random_second_number
66        }
67
68        self.assertEqual(
69            src.calculator.add(
70                two_numbers[0],
71                two_numbers[1]
72            ),
73            self.random_first_number+self.random_second_number
74        )

the terminal shows NameError

NameError: name 'two_numbers' is not defined

GREEN: make it pass


I add a new variable and make a list from the values of the dictionary

 58    def test_calculator_w_list_items(self):
 59        # two_numbers = [
 60        #     self.random_first_number,
 61        #     self.random_second_number
 62        # ]
 63        a_dictionary = {
 64            'x': self.random_first_number,
 65            'y': self.random_second_number
 66        }
 67        two_numbers = list(a_dictionary.values())
 68
 69        self.assertEqual(
 70            src.calculator.add(
 71                two_numbers[0],
 72                two_numbers[1]
 73            ),
 74            self.random_first_number+self.random_second_number
 75        )
 76        self.assertEqual(
 77            src.calculator.divide(
 78                two_numbers[-2],
 79                two_numbers[-1]
 80            ),
 81            self.random_first_number/self.random_second_number
 82        )
 83        self.assertEqual(
 84            src.calculator.multiply(
 85                two_numbers[1],
 86                two_numbers[-1]
 87            ),
 88            self.random_second_number*self.random_second_number
 89        )
 90        self.assertEqual(
 91            src.calculator.subtract(
 92                two_numbers[-2],
 93                two_numbers[0]
 94            ),
 95            self.random_first_number-self.random_first_number
 96        )
 97        self.assertEqual(
 98            src.calculator.add(*two_numbers),
 99            self.random_first_number+self.random_second_number
100        )
101        self.assertEqual(
102            src.calculator.divide(*two_numbers),
103            self.random_first_number/self.random_second_number
104        )
105        self.assertEqual(
106            src.calculator.multiply(*two_numbers),
107            self.random_first_number*self.random_second_number
108        )
109        self.assertEqual(
110            src.calculator.subtract(*two_numbers),
111            self.random_first_number-self.random_second_number
112        )
113
114    def test_calculator_w_dictionary_items(self):

the test is green again


test_calculator_w_for_loops_and_dictionaries

I can use a dictionary with a for loop in test_calculator_sends_message_when_input_is_not_a_number


RED: make it fail


  • I add a new test with a dictionary

    177    def test_calculator_sends_message_when_input_is_not_a_number(self):
    178        error_message = 'brmph?! Numbers only. Try again...'
    179
    180        [
    181            self.assertEqual(
    182                src.calculator.add(data_type, a_random_number()),
    183                error_message
    184            ) for data_type in (
    185                None,
    186                True, False,
    187                str(), 'text',
    188                tuple(), (0, 1, 2, 'n'),
    189                list(), [0, 1, 2, 'n'],
    190                set(), {0, 1, 2, 'n'},
    191                dict(), {'key': 'value'},
    192            )
    193        ]
    194
    195        for data_type in (
    196            None,
    197            True, False,
    198            str(), 'text',
    199            tuple(), (0, 1, 2, 'n'),
    200            list(), [0, 1, 2, 'n'],
    201            set(), {0, 1, 2, 'n'},
    202            dict(), {'key': 'value'},
    203        ):
    204            with self.subTest(data_type=data_type):
    205                self.assertEqual(
    206                    src.calculator.add(
    207                        data_type, a_random_number()
    208                    ),
    209                    error_message
    210                )
    211                self.assertEqual(
    212                    src.calculator.divide(
    213                        data_type, a_random_number()
    214                    ),
    215                    error_message
    216                )
    217                self.assertEqual(
    218                    src.calculator.multiply(
    219                        data_type, a_random_number()
    220                    ),
    221                    error_message
    222                )
    223                self.assertEqual(
    224                    src.calculator.subtract(
    225                        data_type, a_random_number()
    226                    ),
    227                    error_message
    228                )
    229
    230    def test_calculator_w_for_loops_and_dictionaries(self):
    231        arithmetic = {
    232            'addition': src.calculator.add,
    233            'subtraction': src.calculator.subtract,
    234            'multiplication': src.calculator.multiply,
    235            'division': src.calculator.divide,
    236        }
    237
    238
    239# Exceptions seen
    

    arithmetic is a dictionary with the names of the Arithmetic operations as keys, this means

    arithmetic['addition'] is src.calculator.add
    arithmetic['subtract'] is src.calculator.subtract
    arithmetic['multiplication'] is src.calculator.multiply
    arithmetic['division'] is src.calculator.divide
    

    it also means that

    arithmetic['addition'](x, y) is src.calculator.add(x, y)
    arithmetic['subtract'](x, y) is src.calculator.subtract(x, y)
    arithmetic['multiplication'](x, y) is src.calculator.multiply(x, y)
    arithmetic['division'](x, y) is src.calculator.divide(x, y)
    
  • I add for loops with the subTest method

    230    def test_calculator_w_for_loops_and_dictionaries(self):
    231        arithmetic = {
    232            'addition': src.calculator.add,
    233            'subtraction': src.calculator.subtract,
    234            'multiplication': src.calculator.multiply,
    235            'division': src.calculator.divide,
    236        }
    237
    238        for bad_input in (
    239            None,
    240            True, False,
    241            str(), 'text',
    242            tuple(), (0, 1, 2, 'n'),
    243            list(), [0, 1, 2, 'n'],
    244            set(), {0, 1, 2, 'n'},
    245            dict(), {'key': 'value'},
    246        ):
    247            for operation in arithmetic:
    248                with self.subTest(
    249                    operation=operation,
    250                    bad_input=bad_input,
    251                ):
    252                    self.assertEqual(
    253                        arithmetic[operation](
    254                            bad_input, a_random_number()
    255                        ),
    256                        'BOOM!!!'
    257                    )
    258
    259
    260# Exceptions seen
    

    the terminal shows AssertionError

    SUBFAILED(operation='addition', bad_input=None) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    SUBFAILED(operation='subtraction', bad_input=None) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    SUBFAILED(operation='multiplication', bad_input=None) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    SUBFAILED(operation='division', bad_input=None) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    ...
    SUBFAILED(operation='addition', bad_input={'key': 'value'}) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    SUBFAILED(operation='subtraction', bad_input={'key': 'value'}) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    SUBFAILED(operation='multiplication', bad_input={'key': 'value'}) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    SUBFAILED(operation='division', bad_input={'key': 'value'}) ... - AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    =================== 52 failed, 9 passed in R.STs ===================
    

    The second for loop goes over the keys of the dictionary and runs 4 tests for every bad input in the bad_inputs tuple. The two for loops together go every combination of operations and bad inputs

    (addition, None)
    (subtraction, None)
    (multiplication, None)
    (division, None)
    ...
    (addition, tuple())
    (subtraction, tuple())
    (multiplication, tuple())
    (division, tuple())
    ...
    (addition, {'key': 'value'})
    (subtraction, {'key': 'value'})
    (multiplication, {'key': 'value'})
    (division, {'key': 'value'})
    

    this means I do not have to write 52 tests


GREEN: make it pass


I change the expectation to the error message

252                self.assertEqual(
253                    arithmetic[operation](
254                        bad_input, a_random_number()
255                    ),
256                    'brmph?! Numbers only. Try again...'
257                )

the test passes


REFACTOR: make it better


  • I remove test_calculator_sends_message_when_input_is_not_a_number because test_calculator_sends_message_when_input_is_not_a_number covers all its assertions

    165    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    166        not_two_numbers = [0, 1, 2]
    167
    168        with self.assertRaises(TypeError):
    169            src.calculator.add(*not_two_numbers)
    170        with self.assertRaises(TypeError):
    171            src.calculator.divide(*not_two_numbers)
    172        with self.assertRaises(TypeError):
    173            src.calculator.multiply(*not_two_numbers)
    174        with self.assertRaises(TypeError):
    175            src.calculator.subtract(*not_two_numbers)
    176
    177    def test_calculator_w_for_loops_and_dictionaries(self):
    
  • I change the name of test_calculator_w_for_loops_and_dictionaries to test_calculator_sends_message_when_input_is_not_a_number to say what it does

    177    def test_calculator_sends_message_when_input_is_not_a_number(self):
    178        arithmetic = {
    179            'addition': src.calculator.add,
    180            'subtraction': src.calculator.subtract,
    181            'multiplication': src.calculator.multiply,
    182            'division': src.calculator.divide,
    183        }
    184
    185        for bad_input in (
    186            None,
    187            True, False,
    188            str(), 'text',
    189            tuple(), (0, 1, 2, 'n'),
    190            list(), [0, 1, 2, 'n'],
    191            set(), {0, 1, 2, 'n'},
    192            dict(), {'key': 'value'},
    193        ):
    194            for operation in arithmetic:
    195                with self.subTest(
    196                    operation=operation,
    197                    bad_input=bad_input,
    198                ):
    199                    self.assertEqual(
    200                        arithmetic[operation](
    201                            bad_input, a_random_number()
    202                        ),
    203                        'brmph?! Numbers only. Try again...'
    204                    )
    205
    206
    207# Exceptions seen
    

    this solution is not as easy to read as what was there before. Is there a better way?


test_calculator_functions

I want to use a dictionary to write one test that covers all the 4 arithmetic functions: addition, subtraction, division and multiplication and check their results are correct


RED: make it pass


  • I add a new test

    177    def test_calculator_sends_message_when_input_is_not_a_number(self):
    178        arithmetic = {
    179            'addition': src.calculator.add,
    180            'subtraction': src.calculator.subtract,
    181            'multiplication': src.calculator.multiply,
    182            'division': src.calculator.divide,
    183        }
    184
    185        for bad_input in (
    186            None,
    187            True, False,
    188            str(), 'text',
    189            tuple(), (0, 1, 2, 'n'),
    190            list(), [0, 1, 2, 'n'],
    191            set(), {0, 1, 2, 'n'},
    192            dict(), {'key': 'value'},
    193        ):
    194            for operation in arithmetic:
    195                with self.subTest(
    196                    operation=operation,
    197                    bad_input=bad_input,
    198                ):
    199                    self.assertEqual(
    200                        arithmetic[operation](
    201                            bad_input, a_random_number()
    202                        ),
    203                        'brmph?! Numbers only. Try again...'
    204                    )
    205
    206    def test_calculator_functions(self):
    207        arithmetic = {
    208            'addition': src.calculator.add,
    209            'subtraction': src.calculator.subtract,
    210            'division': src.calculator.divide,
    211            'multiplication': src.calculator.multiply,
    212        }
    213
    214        for operation in arithmetic:
    215            with self.subTest(operation=operation):
    216                self.assertEqual(
    217                    arithmetic[operation](
    218                        self.random_first_number,
    219                        self.random_second_number
    220                    ),
    221                    'BOOM!!!'
    222                )
    223
    224
    225# Exceptions seen
    

    the terminal shows AssertionError for the 4 arithmetic operations

    SUBFAILED(operation='addition') ...       - AssertionError: QRS.TUVWXYZABCDEF != 'BOOM!!!'
    SUBFAILED(operation='subtraction') ...    - AssertionError: GHI.JKLMNOPQRSTUVWX != 'BOOM!!!'
    SUBFAILED(operation='division') ...       - AssertionError: Y.ABCDEFGHIJKLMNOP != 'BOOM!!!'
    SUBFAILED(operation='multiplication') ... - AssertionError: QRSTUV.WXYZABCDEFG != 'BOOM!!!'
    

    how do I add the results?


GREEN: make it pass


  • I add a dictionary for the calculations of each operation

    206    def test_calculator_functions(self):
    207        arithmetic = {
    208            'addition': src.calculator.add,
    209            'subtraction': src.calculator.subtract,
    210            'division': src.calculator.divide,
    211            'multiplication': src.calculator.multiply,
    212        }
    213
    214        expectations = {
    215            'addition': (
    216                self.random_first_number+self.random_second_number
    217            ),
    218            'subtraction': (
    219                self.random_first_number-self.random_second_number
    220            ),
    221            'division': (
    222                self.random_first_number/self.random_second_number
    223            ),
    224            'multiplication': (
    225                self.random_first_number*self.random_second_number
    226            )
    227        }
    228
    229        for operation in arithmetic:
    
  • I use the new dictionary for the calculation in the assertion

    229        for operation in arithmetic:
    230            with self.subTest(operation=operation):
    231                self.assertEqual(
    232                    arithmetic[operation](
    233                        self.random_first_number,
    234                        self.random_second_number
    235                    ),
    236                    expectations[operation]
    237                )
    

    the test passes.

This test goes through every operation in the arithmetic dictionary then calls the function that is its value with self.random_first_number and self.random_second_number as input, and checks if the result is the value for the operation key in the expectations dictionary. I think of it as

self.assertEqual(
    arithmetic[operation](x, y),
    expectations[operation]
)
  • operation can be addition, subtraction, multiplication or division

  • in this case x is self.first_random_number

  • in this case y is self.second_random_number

this means all these statements are the same

  • for addition

    arithmetic['addition'](x, y)
    src.calculator.add(x, y)
    expectations['addition']
    x + y
    
  • for subtraction

    arithmetic['subtraction'](x, y)
    src.calculator.add(x, y)
    expectations['subtraction']
    x - y
    
  • for multiplication

    arithmetic['multiplication'](x, y)
    src.calculator.add(x, y)
    expectations['multiplication']
    x * y
    
  • for division

    arithmetic['division'](x, y)
    src.calculator.add(x, y)
    expectations['division']
    x / y
    

REFACTOR: make it better


  • I can put the two dictionaries in this test together because they have the same keys

    214        expectations = {
    215            'addition': (
    216                self.random_first_number+self.random_second_number
    217            ),
    218            'subtraction': (
    219                self.random_first_number-self.random_second_number
    220            ),
    221            'division': (
    222                self.random_first_number/self.random_second_number
    223            ),
    224            'multiplication': (
    225                self.random_first_number*self.random_second_number
    226            )
    227        }
    228
    229        x = self.random_first_number
    230        y = self.random_second_number
    231        arithmetic_tests = {
    232            'addition': {
    233                'function': src.calculator.add,
    234                'expectation': x+y,
    235            },
    236            'subtraction': {
    237                'function': src.calculator.subtract,
    238                'expectation': x-y,
    239            },
    240            'division': {
    241                'function': src.calculator.divide,
    242                'expectation': x/y,
    243            },
    244            'multiplication': {
    245                'function': src.calculator.multiply,
    246                'expectation': x*y,
    247            },
    248        }
    249
    250        for operation in arithmetic:
    
  • I add a new assertion in a for loop with the subTest method

    250        for operation in arithmetic:
    251            with self.subTest(operation=operation):
    252                self.assertEqual(
    253                    arithmetic[operation](
    254                        self.random_first_number,
    255                        self.random_second_number
    256                    ),
    257                    expectations[operation]
    258                )
    259
    260        for operation in arithmetic_tests:
    261            with self.subTest(operation=operation):
    262                self.assertEqual(
    263                    arithmetic_tests[operation]['function'](x, y),
    264                    'BOOM!!!'
    265                )
    266
    267
    268# Exceptions seen
    269
    270    def test_calculator_sends_message_when_input_is_not_a_number(self):
    

    the terminal shows AssertionError:

    SUBFAILED(operation='addition') ...       - AssertionError: HIJ.KLMNOPQRSTUVW != 'BOOM!!!'
    SUBFAILED(operation='subtraction') ...    - AssertionError: XYZA.BCDEFGHIJKLMN != 'BOOM!!!'
    SUBFAILED(operation='division') ...       - AssertionError: NO.PQRSTUVWXYZABCDE != 'BOOM!!!'
    SUBFAILED(operation='multiplication') ... - AssertionError: FGHIJKLM.NOPQRSTUVW != 'BOOM!!!'
    
  • I change the expectation

    260        for operation in arithmetic_tests:
    261            with self.subTest(operation=operation):
    262                self.assertEqual(
    263                    arithmetic_tests[operation]['function'](x, y),
    264                    arithmetic_tests[operation]['expectation']
    265                )
    

    the test passes

  • I remove the other dictionaries and for loop

    206    def test_calculator_functions(self):
    207        x = self.random_first_number
    208        y = self.random_second_number
    209        arithmetic_tests = {
    210            'addition': {
    211                'function': src.calculator.add,
    212                'expectation': x+y,
    213            },
    214            'subtraction': {
    215                'function': src.calculator.subtract,
    216                'expectation': x-y,
    217            },
    218            'division': {
    219                'function': src.calculator.divide,
    220                'expectation': x/y,
    221            },
    222            'multiplication': {
    223                'function': src.calculator.multiply,
    224                'expectation': x*y,
    225            },
    226        }
    227
    228        for operation in arithmetic_tests:
    229            with self.subTest(operation=operation):
    230                self.assertEqual(
    231                    arithmetic_tests[operation]['function'](x, y),
    232                    arithmetic_tests[operation]['expectation']
    233                )
    234
    235
    236# Exceptions seen
    

    much better


  • I remove test_addition, test_subtraction and test_multiplication

    10class TestCalculator(unittest.TestCase):
    11
    12    def setUp(self):
    13        self.random_first_number = a_random_number()
    14        self.random_second_number = a_random_number()
    15
    16    def test_division(self):
    

    I have to handle ZeroDivisionError in test_calculator_functions before I can remove test_division

  • I change y to 0 in test_calculator_functions

    179    def test_calculator_functions(self):
    180        x = self.random_first_number
    181        # y = self.random_second_number
    182        y = 0
    183
    184        arithmetic_tests = {
    

    the terminal shows ZeroDivisionError

    ZeroDivisionError: float division by zero
    

    good

  • I add an exception handler to make a variable for the result of division

    179    def test_calculator_functions(self):
    180        x = self.random_first_number
    181        # y = self.random_second_number
    182        y = 0
    183
    184        try:
    185            division_result = x / y
    186        except ZeroDivisionError:
    187            division_result = 'BOOM!!!'
    188
    189        arithmetic_tests = {
    

    here is what the exception handler does

    try:
        division_result = x / y
    

    try to point division_result to the result of dividing x by y

    except ZeroDivisionError:
        division_result = 'BOOM!!!'
    

    if ZeroDivisionError is raised when trying to divide x by y, point division_result to 'BOOM!!!'

  • I use division_result in the arithmetic_tests dictionary

    189        arithmetic_tests = {
    190            'addition': {
    191                'function': src.calculator.add,
    192                'expectation': x+y,
    193            },
    194            'subtraction': {
    195                'function': src.calculator.subtract,
    196                'expectation': x-y,
    197            },
    198            'division': {
    199                'function': src.calculator.divide,
    200                # 'expectation': x/y,
    201                'expectation': division_result,
    202            },
    203            'multiplication': {
    204                'function': src.calculator.multiply,
    205                'expectation': x*y,
    206            },
    207        }
    

    the terminal shows AssertionError

    AssertionError: 'brmph?! I cannot divide by 0. Try again...' != 'BOOM!!!'
    
  • I change 'BOOM!!!' to the error message

    184        try:
    185            division_result = x / y
    186        except ZeroDivisionError:
    187            division_result = 'brmph?! I cannot divide by 0. Try again...'
    

    the test passes. Progress

  • I change y back to self.random_second_number

    179    def test_calculator_functions(self):
    180        x = self.random_first_number
    181        y = self.random_second_number
    182
    183        try:
    184            division_result = x / y
    185        except ZeroDivisionError:
    186            division_result = 'brmph?! I cannot divide by 0. Try again...'
    187
    188        arithmetic_tests = {
    189            'addition': {
    190                'function': src.calculator.add,
    191                'expectation': x+y,
    192            },
    193            'subtraction': {
    194                'function': src.calculator.subtract,
    195                'expectation': x-y,
    196            },
    197            'division': {
    198                'function': src.calculator.divide,
    199                'expectation': division_result,
    200            },
    201            'multiplication': {
    202                'function': src.calculator.multiply,
    203                'expectation': x*y,
    204            },
    205        }
    206
    207        for operation in arithmetic_tests:
    208            with self.subTest(operation=operation):
    209                self.assertEqual(
    210                    arithmetic_tests[operation]['function'](x, y),
    211                    arithmetic_tests[operation]['expectation']
    212                )
    213
    214
    215# Exceptions seen
    

    the test is still green


I can put the arithmetic_tests dictionary from test_calculator_functions and the arithmetic dictionary from test_calculator_sends_message_when_input_is_not_a_number together because they have the same keys.

  • I add the variables from test_calculator_functions to the setUp method

    12    def setUp(self):
    13        self.random_first_number = a_random_number()
    14        self.random_second_number = a_random_number()
    15
    16        x = self.random_first_number
    17        y = self.random_second_number
    18
    19        try:
    20            division_result = x / y
    21        except ZeroDivisionError:
    22            division_result = 'brmph?! I cannot divide by 0. Try again...'
    23
    24        self.arithmetic_tests = {
    25            'addition': {
    26                'function': src.calculator.add,
    27                'expectation': x+y,
    28            },
    29            'subtraction': {
    30                'function': src.calculator.subtract,
    31                'expectation': x-y,
    32            },
    33            'division': {
    34                'function': src.calculator.divide,
    35                'expectation': division_result,
    36            },
    37            'multiplication': {
    38                'function': src.calculator.multiply,
    39                'expectation': x*y,
    40            }
    41        }
    
  • I use the new class attribute in the for loop in test_calculator_functions

    219        # for operation in arithmetic_tests:
    220        for operation in self.arithmetic_tests:
    221            with self.subTest(operation=operation):
    222                self.assertEqual(
    223                    arithmetic_tests[operation]['function'](x, y),
    224                    arithmetic_tests[operation]['expectation']
    225                )
    

    the test is still green

  • I use it in the assertion

    222                self.assertEqual(
    223                    # arithmetic_tests[operation]['function'](x, y),
    224                    self.arithmetic_tests[operation]['function'](
    225                        x, y
    226                    ),
    227                    arithmetic_tests[operation]['expectation']
    228                )
    

    still green

  • I use it for the expectation

    222                self.assertEqual(
    223                    # arithmetic_tests[operation]['function'](x, y),
    224                    self.arithmetic_tests[operation]['function'](
    225                        x, y
    226                    ),
    227                    # arithmetic_tests[operation]['expectation']
    228                    self.arithmetic_tests[operation]['expectation']
    229                )
    

    the test is still green

  • I comment out the arithmetic_tests dictionary in test_calculator_functions

    195        try:
    196            division_result = x / y
    197        except ZeroDivisionError:
    198            division_result = 'brmph?! I cannot divide by 0. Try again...'
    199
    200        # arithmetic_tests = {
    201        #     'addition': {
    202        #         'function': src.calculator.add,
    203        #         'expectation': x+y,
    204        #     },
    205        #     'subtraction': {
    206        #         'function': src.calculator.subtract,
    207        #         'expectation': x-y,
    208        #     },
    209        #     'division': {
    210        #         'function': src.calculator.divide,
    211        #         'expectation': division_result,
    212        #     },
    213        #     'multiplication': {
    214        #         'function': src.calculator.multiply,
    215        #         'expectation': x*y,
    216        #     },
    217        # }
    218
    219        # for operation in arithmetic_tests:
    

    still green

  • I comment out the exception handler

    191    def test_calculator_functions(self):
    192        x = self.random_first_number
    193        y = self.random_second_number
    194
    195        # try:
    196        #     division_result = x / y
    197        # except ZeroDivisionError:
    198        #     division_result = 'brmph?! I cannot divide by 0. Try again...'
    

    green

  • I comment out the x and y variables

    191    def test_calculator_functions(self):
    192        # x = self.random_first_number
    193        # y = self.random_second_number
    

    the terminal shows NameError

    SUBFAILED(operation='addition') ...       - NameError: name 'x' is not defined
    SUBFAILED(operation='subtraction') ...    - NameError: name 'x' is not defined
    SUBFAILED(operation='division') ...       - NameError: name 'x' is not defined
    SUBFAILED(operation='multiplication') ... - NameError: name 'x' is not defined
    
  • I undo the change

    191    def test_calculator_functions(self):
    192        x = self.random_first_number
    193        y = self.random_second_number
    

    the test is green again

  • I change the inputs in the assertion

    222                self.assertEqual(
    223                    # arithmetic_tests[operation]['function'](x, y),
    224                    self.arithmetic_tests[operation]['function'](
    225                        # x, y
    226                        self.random_first_number,
    227                        self.random_second_number
    228                    ),
    229                    # arithmetic_tests[operation]['expectation']
    230                    self.arithmetic_tests[operation]['expectation']
    231                )
    

    the test is still green

  • I comment out x and y again

    191    def test_calculator_functions(self):
    192        # x = self.random_first_number
    193        # y = self.random_second_number
    

    green

  • I remove the commented lines

    191    def test_calculator_functions(self):
    192        for operation in self.arithmetic_tests:
    193            with self.subTest(operation=operation):
    194                self.assertEqual(
    195                    self.arithmetic_tests[operation]['function'](
    196                        self.random_first_number,
    197                        self.random_second_number
    198                    ),
    199                    self.arithmetic_tests[operation]['expectation']
    200                )
    201
    202
    203# Exceptions seen
    

    the test is still green


  • I use the new class attribute in the for loop in test_calculator_sends_message_when_input_is_not_a_number

    162    def test_calculator_sends_message_when_input_is_not_a_number(self):
    163        arithmetic = {
    164            'addition': src.calculator.add,
    165            'subtraction': src.calculator.subtract,
    166            'multiplication': src.calculator.multiply,
    167            'division': src.calculator.divide,
    168        }
    169
    170        for bad_input in (
    171            None,
    172            True, False,
    173            str(), 'text',
    174            tuple(), (0, 1, 2, 'n'),
    175            list(), [0, 1, 2, 'n'],
    176            set(), {0, 1, 2, 'n'},
    177            dict(), {'key': 'value'},
    178        ):
    179            # for operation in arithmetic:
    180            for operation in self.arithmetic_tests:
    181                with self.subTest(
    182                    operation=operation,
    183                    bad_input=bad_input,
    184                ):
    185                    self.assertEqual(
    186                        arithmetic[operation](
    187                            bad_input, a_random_number()
    188                        ),
    189                        'brmph?! Numbers only. Try again...'
    190                    )
    191
    192    def test_calculator_functions(self):
    

    still green

  • I use it in the assertion

    185                    self.assertEqual(
    186                        # arithmetic[operation](
    187                        self.arithmetic_tests[operation]['function'](
    188                            bad_input, a_random_number()
    189                        ),
    190                        'brmph?! Numbers only. Try again...'
    191                    )
    

    green

  • I remove the commented lines and the arithmetic variable because it is no longer used

    162    def test_calculator_sends_message_when_input_is_not_a_number(self):
    163        for bad_input in (
    164            None,
    165            True, False,
    166            str(), 'text',
    167            tuple(), (0, 1, 2, 'n'),
    168            list(), [0, 1, 2, 'n'],
    169            set(), {0, 1, 2, 'n'},
    170            dict(), {'key': 'value'},
    171        ):
    172            for operation in self.arithmetic_tests:
    173                with self.subTest(
    174                    operation=operation,
    175                    bad_input=bad_input,
    176                ):
    177                    self.assertEqual(
    178                        self.arithmetic_tests[operation]['function'](
    179                            bad_input, a_random_number()
    180                        ),
    181                        'brmph?! Numbers only. Try again...'
    182                    )
    183
    184    def test_calculator_functions(self):
    

    green around the roses


  • I add a for loop to use the self.arithmetic_tests dictionary in test_calculator_raises_type_error_when_given_more_than_two_inputs

    150    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    151        not_two_numbers = [0, 1, 2]
    152
    153        for operation in self.arithmetic_tests:
    154            with self.subTest(operation=operation):
    155                self.arithmetic_tests[operation]['function'](
    156                    **not_two_numbers
    157                )
    158
    159        with self.assertRaises(TypeError):
    160            src.calculator.add(*not_two_numbers)
    161        with self.assertRaises(TypeError):
    162            src.calculator.divide(*not_two_numbers)
    163        with self.assertRaises(TypeError):
    164            src.calculator.multiply(*not_two_numbers)
    165        with self.assertRaises(TypeError):
    166            src.calculator.subtract(*not_two_numbers)
    

    the terminal shows TypeError for all 4 cases

    SUBFAILED(operation='addition') ...       - TypeError: src.calculator.numbers_only.<locals>.decorator() argument after ** must be a m...
    SUBFAILED(operation='subtraction') ...    - TypeError: src.calculator.numbers_only.<locals>.decorator() argument after ** must be a m...
    SUBFAILED(operation='division') ...       - TypeError: src.calculator.numbers_only.<locals>.decorator() argument after ** must be a m...
    SUBFAILED(operation='multiplication') ... - TypeError: src.calculator.numbers_only.<locals>.decorator() argument after ** must be a m...
    

    lovely! The test works

  • I add the assertRaises method

    153        for operation in self.arithmetic_tests:
    154            with self.subTest(operation=operation):
    155                with self.assertRaises(TypeError):
    156                    self.arithmetic_tests[operation]['function'](
    157                        **not_two_numbers
    158                    )
    

    the test passes

  • I remove the other assertions

    171    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    172        not_two_numbers = [0, 1, 2]
    173
    174        for operation in self.arithmetic_tests:
    175            with self.subTest(operation=operation):
    176                with self.assertRaises(TypeError):
    177                    self.arithmetic_tests[operation]['function'](
    178                        **not_two_numbers
    179                    )
    180
    181    def test_calculator_sends_message_when_input_is_not_a_number(self):
    

    the test is still green

  • I use the list for the variable in the assertion

    156                    self.arithmetic_tests[operation]['function'](
    157                        # **not_two_numbers
    158                        [0, 1, 2]
    159                    )
    

    still green

  • I put the two with statements together

    153        for operation in self.arithmetic_tests:
    154            # with self.subTest(operation=operation):
    155            #     with self.assertRaises(TypeError):
    156            with (
    157                self.subTest(operation=operation),
    158                self.assertRaises(TypeError),
    159            ):
    160                self.arithmetic_tests[operation]['function'](
    161                    # **not_two_numbers
    162                    [0, 1, 2]
    163                )
    

    green

  • I remove the commented lines and not_two_numbers variable

    150    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    151        for operation in self.arithmetic_tests:
    152            with (
    153                self.subTest(operation=operation),
    154                self.assertRaises(TypeError),
    155            ):
    156                self.arithmetic_tests[operation]['function'](
    157                    [0, 1, 2]
    158                )
    159
    160    def test_calculator_sends_message_when_input_is_not_a_number(self):
    

    still green


  • I add a for loop to use the self.arithmetic_tests dictionary in test_calculator_w_dictionary_items

     99    def test_calculator_w_dictionary_items(self):
    100        two_numbers = {
    101            'first_input': self.random_first_number,
    102            'second_input': self.random_second_number,
    103        }
    104
    105        for operation in self.arithmetic_tests:
    106            with self.subTest(operation=operation):
    107                self.assertEqual(
    108                    self.arithmetic_tests[operation]['function'](
    109                        **two_numbers
    110                    ),
    111                    'BOOM!!!'
    112                )
    113
    114        self.assertEqual(
    115            src.calculator.add(
    116                two_numbers['first_input'],
    117                two_numbers['second_input']
    118            ),
    119            self.random_first_number+self.random_second_number
    120        )
    121        self.assertEqual(
    122            src.calculator.divide(
    123                two_numbers['first_input'],
    124                two_numbers['second_input']
    125            ),
    126            self.random_first_number/self.random_second_number
    127        )
    128        self.assertEqual(
    129            src.calculator.multiply(
    130                two_numbers['second_input'],
    131                two_numbers['second_input']
    132            ),
    133            self.random_second_number*self.random_second_number
    134        )
    135        self.assertEqual(
    136            src.calculator.subtract(
    137                two_numbers['first_input'],
    138                two_numbers['first_input']
    139            ),
    140            self.random_first_number-self.random_first_number
    141        )
    142        self.assertEqual(
    143            src.calculator.add(**two_numbers),
    144            self.random_first_number+self.random_second_number
    145        )
    146        self.assertEqual(
    147            src.calculator.divide(**two_numbers),
    148            self.random_first_number/self.random_second_number
    149        )
    150        self.assertEqual(
    151            src.calculator.multiply(**two_numbers),
    152            self.random_first_number*self.random_second_number
    153        )
    154        self.assertEqual(
    155            src.calculator.subtract(**two_numbers),
    156            self.random_first_number-self.random_second_number
    157        )
    158
    159    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the terminal shows AssertionError for the 4 operations

    SUBFAILED(operation='addition') ...       - AssertionError: XYZ.ABCDEFGHIJKLM != 'BOOM!!!'
    SUBFAILED(operation='subtraction') ...    - AssertionError: NOP.QRSTUVWXYZABC != 'BOOM!!!'
    SUBFAILED(operation='division') ...       - AssertionError: D.EFGHIJKLMNOPQRS != 'BOOM!!!'
    SUBFAILED(operation='multiplication') ... - AssertionError: TUVWX.YZABCDEFGHI != 'BOOM!!!'
    
  • I change the expectation

    107                self.assertEqual(
    108                    self.arithmetic_tests[operation]['function'](
    109                        **two_numbers
    110                    ),
    111                    self.arithmetic_tests[operation]['expectation']
    112                )
    

    the test passes

  • I remove all the assertions that have **two_numbers

     99    def test_calculator_w_dictionary_items(self):
    100        two_numbers = {
    101            'first_input': self.random_first_number,
    102            'second_input': self.random_second_number,
    103        }
    104
    105        for operation in self.arithmetic_tests:
    106            with self.subTest(operation=operation):
    107                self.assertEqual(
    108                    self.arithmetic_tests[operation]['function'](
    109                        **two_numbers
    110                    ),
    111                    self.arithmetic_tests[operation]['expectation']
    112                )
    113
    114        self.assertEqual(
    115            src.calculator.add(
    116                two_numbers['first_input'],
    117                two_numbers['second_input']
    118            ),
    119            self.random_first_number+self.random_second_number
    120        )
    121        self.assertEqual(
    122            src.calculator.divide(
    123                two_numbers['first_input'],
    124                two_numbers['second_input']
    125            ),
    126            self.random_first_number/self.random_second_number
    127        )
    128        self.assertEqual(
    129            src.calculator.multiply(
    130                two_numbers['second_input'],
    131                two_numbers['second_input']
    132            ),
    133            self.random_second_number*self.random_second_number
    134        )
    135        self.assertEqual(
    136            src.calculator.subtract(
    137                two_numbers['first_input'],
    138                two_numbers['first_input']
    139            ),
    140            self.random_first_number-self.random_first_number
    141        )
    142
    143    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the test is still green


  • I add a for loop to use the self.arithmetic_tests dictionary in test_calculator_w_list_items

     43    def test_calculator_w_list_items(self):
     44        # two_numbers = [
     45        #     self.random_first_number,
     46        #     self.random_second_number
     47        # ]
     48        a_dictionary = {
     49            'x': self.random_first_number,
     50            'y': self.random_second_number
     51        }
     52        two_numbers = list(a_dictionary.values())
     53
     54        for operation in self.arithmetic_tests:
     55            with self.subTest(operation=operation):
     56                self.assertEqual(
     57                    self.arithmetic_tests[operation]['function'](
     58                        *two_numbers
     59                    ),
     60                    'BOOM!!!'
     61                )
     62
     63        self.assertEqual(
     64            src.calculator.add(
     65                two_numbers[0],
     66                two_numbers[1]
     67            ),
     68            self.random_first_number+self.random_second_number
     69        )
     70        self.assertEqual(
     71            src.calculator.divide(
     72                two_numbers[-2],
     73                two_numbers[-1]
     74            ),
     75            self.random_first_number/self.random_second_number
     76        )
     77        self.assertEqual(
     78            src.calculator.multiply(
     79                two_numbers[1],
     80                two_numbers[-1]
     81            ),
     82            self.random_second_number*self.random_second_number
     83        )
     84        self.assertEqual(
     85            src.calculator.subtract(
     86                two_numbers[-2],
     87                two_numbers[0]
     88            ),
     89            self.random_first_number-self.random_first_number
     90        )
     91        self.assertEqual(
     92            src.calculator.add(*two_numbers),
     93            self.random_first_number+self.random_second_number
     94        )
     95        self.assertEqual(
     96            src.calculator.divide(*two_numbers),
     97            self.random_first_number/self.random_second_number
     98        )
     99        self.assertEqual(
    100            src.calculator.multiply(*two_numbers),
    101            self.random_first_number*self.random_second_number
    102        )
    103        self.assertEqual(
    104            src.calculator.subtract(*two_numbers),
    105            self.random_first_number-self.random_second_number
    106        )
    107
    108    def test_calculator_w_dictionary_items(self):
    

    the terminal shows AssertionError for the 4 operations

    SUBFAILED(operation='addition') ...       - AssertionError: JK.LMNOPQRSTUVWXYZ != 'BOOM!!!'
    SUBFAILED(operation='subtraction') ...    - AssertionError: ABCD.EFGHIJKLMNOPQ != 'BOOM!!!'
    SUBFAILED(operation='division') ...       - AssertionError: R.STUVWXYZABCDEFGH != 'BOOM!!!'
    SUBFAILED(operation='multiplication') ... - AssertionError: IJKLMN.OPQRSTUVWXY != 'BOOM!!!'
    
  • I change the expectation

    56                self.assertEqual(
    57                    self.arithmetic_tests[operation]['function'](
    58                        *two_numbers
    59                    ),
    60                    self.arithmetic_tests[operation]['expectation']
    61                )
    

    the test passes

  • I remove all the other assertions that have *two_numbers

    43    def test_calculator_w_list_items(self):
    44        # two_numbers = [
    45        #     self.random_first_number,
    46        #     self.random_second_number
    47        # ]
    48        a_dictionary = {
    49            'x': self.random_first_number,
    50            'y': self.random_second_number
    51        }
    52        two_numbers = list(a_dictionary.values())
    53
    54        for operation in self.arithmetic_tests:
    55            with self.subTest(operation=operation):
    56                self.assertEqual(
    57                    self.arithmetic_tests[operation]['function'](
    58                        *two_numbers
    59                    ),
    60                    self.arithmetic_tests[operation]['expectation']
    61                )
    62
    63        self.assertEqual(
    64            src.calculator.add(
    65                two_numbers[0],
    66                two_numbers[1]
    67            ),
    68            self.random_first_number+self.random_second_number
    69        )
    70        self.assertEqual(
    71            src.calculator.divide(
    72                two_numbers[-2],
    73                two_numbers[-1]
    74            ),
    75            self.random_first_number/self.random_second_number
    76        )
    77        self.assertEqual(
    78            src.calculator.multiply(
    79                two_numbers[1],
    80                two_numbers[-1]
    81            ),
    82            self.random_second_number*self.random_second_number
    83        )
    84        self.assertEqual(
    85            src.calculator.subtract(
    86                two_numbers[-2],
    87                two_numbers[0]
    88            ),
    89            self.random_first_number-self.random_first_number
    90        )
    91
    92    def test_calculator_w_dictionary_items(self):
    

    the test is still green

I think it is time to take nap. That was a lot.


close the project

  • I close test_calculator.py in the editor

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

    (.venv) .../pumping_python/calculator
    
  • I deactivate the virtual environment

    deactivate
    

    the terminal goes back to the command line, (.venv) is no longer on the left side

    .../pumping_python/calculator
    
  • I change directory to the parent of calculator

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

I added these tests for the calculator program with dictionaries which made testing the program easier


code from the chapter

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


what is next?

you know

I edited makePythonTdd.sh or makePythonTdd.ps1 for the last few projects. I want to automate the process so that I can call the program and it does all the steps for me when I give it the name of the project.

Would you like to know how to make a Python Test Driven Development environment automatically with variables?


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