how to make a calculator part 6

I can use a dictionary to test the calculator functions as long as its values are numbers


open the project

pytest-watch

the terminal shows

rootdir: .../pumping_python/calculator
collected 7 items

tests/test_calculator.py .......                                     [100%]

============================ 7 passed in X.YZs =============================
  • I hold ctrl on the keyboard and 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 to use a dictionary to test the calculator

119          self.assertEqual(
120              src.calculator.subtract(*a_list),
121              self.random_first_number-self.random_second_number
122          )
123
124      def test_calculator_w_dictionary_items(self):
125          two_numbers = {
126              'x': self.random_first_number,
127              'y': self.random_second_number,
128          }
129
130          self.assertEqual(
131              src.calculator.add(two_numbers['x'], two_numbers['y']),
132              self.random_first_number+self.random_first_number
133          )
134
135      def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
136          not_two_numbers = [0, 1, 2]

the terminal shows AssertionError

AssertionError: ABC.DEFGHIJKLMNOPQ != RST.UVWXYZABCDEFG

GREEN: make it pass

I change the expectation to the right calculation

130        self.assertEqual(
131            src.calculator.add(two_numbers['x'], two_numbers['y']),
132            self.random_first_number+self.random_second_number
133        )

the test passes

REFACTOR: make it better

  • I add an assertion for the divide function

    130        self.assertEqual(
    131            src.calculator.add(two_numbers['x'], two_numbers['y']),
    132            self.random_first_number+self.random_second_number
    133        )
    134        self.assertEqual(
    135            src.calculator.divide(two_numbers['x'], two_numbers['y']),
    136            self.random_first_number*self.random_second_number
    137        )
    

    the terminal shows AssertionError

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

    134        self.assertEqual(
    135            src.calculator.divide(two_numbers['x'], two_numbers['y']),
    136            self.random_first_number/self.random_second_number
    137        )
    

    the test passes

  • I add another assertion

    134        self.assertEqual(
    135            src.calculator.divide(two_numbers['x'], two_numbers['y']),
    136            self.random_first_number/self.random_second_number
    137        )
    138        self.assertEqual(
    139            src.calculator.multiply(two_numbers['y'], two_numbers['y']),
    140            self.random_first_number*self.random_second_number
    141        )
    

    the terminal shows AssertionError

    AssertionError: EFGHIJ.KLMNOPQRSTU != VWXYZ.ABCDEFGHIJKL
    
  • I change the expectation

    123        self.assertEqual(
    124            src.calculator.multiply(two_numbers['y'], two_numbers['y']),
    125            self.random_second_number*self.random_second_number
    126        )
    

    the test passes

  • I add an assertion for the subtract function

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

    the terminal shows AssertionError

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

    142        self.assertEqual(
    143            src.calculator.subtract(two_numbers['x'], two_numbers['x']),
    144            self.random_first_number-self.random_first_number
    145        )
    

    the test passes

  • Python allows me use a double starred expression like I did in test_functions_w_unknown_arguments. I add an assertion with it

    142        self.assertEqual(
    143            src.calculator.subtract(two_numbers['x'], two_numbers['x']),
    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        )
    

    the terminal shows TypeError

    TypeError: only_takes_numbers.<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. I change x and y to first_input and second_input in the test

    124    def test_calculator_w_dictionary_items(self):
    125        two_numbers = {
    126            'first_input': self.random_first_number,
    127            'second_input': self.random_second_number,
    128        }
    129
    130        self.assertEqual(
    131            src.calculator.add(two_numbers['first_input'], two_numbers['second_input']),
    132            self.random_first_number+self.random_second_number
    133        )
    134        self.assertEqual(
    135            src.calculator.divide(two_numbers['first_input'], two_numbers['second_input']),
    136            self.random_first_number/self.random_second_number
    137        )
    138        self.assertEqual(
    139            src.calculator.multiply(two_numbers['second_input'], two_numbers['second_input']),
    140            self.random_second_number*self.random_second_number
    141        )
    142        self.assertEqual(
    143            src.calculator.subtract(two_numbers['first_input'], two_numbers['first_input']),
    144            self.random_first_number-self.random_first_number
    145        )
    

    the terminal shows AssertionError

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

    146        self.assertEqual(
    147            src.calculator.add(**two_numbers),
    148            self.random_first_number+self.random_second_number
    149        )
    

    the test passes

  • I add another assertion

    146        self.assertEqual(
    147            src.calculator.add(**two_numbers),
    148            self.random_first_number+self.random_second_number
    149        )
    150        self.assertEqual(
    151            src.calculator.divide(**two_numbers),
    152            self.random_first_number*self.random_second_number
    153        )
    

    the terminal shows AssertionError

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

    150        self.assertEqual(
    151            src.calculator.divide(**two_numbers),
    152            self.random_first_number/self.random_second_number
    153        )
    

    the test passes

  • I add an assertion for the multiply function

    150        self.assertEqual(
    151            src.calculator.divide(**two_numbers),
    152            self.random_first_number/self.random_second_number
    153        )
    154        self.assertEqual(
    155            src.calculator.multiply(**two_numbers),
    156            self.random_first_number/self.random_second_number
    157        )
    

    the terminal shows AssertionError

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

    154        self.assertEqual(
    155            src.calculator.multiply(**two_numbers),
    156            self.random_first_number*self.random_second_number
    157        )
    

    the test passes

  • I add the next assertion

    154        self.assertEqual(
    155            src.calculator.multiply(**two_numbers),
    156            self.random_first_number*self.random_second_number
    157        )
    158        self.assertEqual(
    159            src.calculator.subtract(**two_numbers),
    160            self.random_first_number+self.random_second_number
    161        )
    

    the terminal shows AssertionError

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

    158        self.assertEqual(
    159            src.calculator.subtract(**two_numbers),
    160            self.random_first_number-self.random_second_number
    161        )
    

    the test passes

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

    88    def test_calculator_w_list_items(self):
    89        # two_numbers = [self.random_first_number, self.random_second_number]
    90        a_dictionary = {
    91            'x': self.random_first_number,
    92            'y': self.random_second_number
    93        }
    94        two_numbers = list(a_dictionary.values())
    95
    96        self.assertEqual(
    97            src.calculator.add(two_numbers[0], two_numbers[1]),
    98            self.random_first_number+self.random_second_number
    99        )
    

    the test is still green

  • I can also use a dictionary with a for loop to make test_calculator_sends_message_when_input_is_not_a_number more complex and simpler

    58def test_calculator_sends_message_when_input_is_not_a_number(self):
    59    error_message = 'Excuse me?! Numbers only. Try again...'
    60
    61    arithmetic = {
    62        'add': src.calculator.add,
    63        'subtract': src.calculator.subtract,
    64        'multiply': src.calculator.multiply,
    65        'divide': src.calculator.divide
    66    }
    67
    68    for data in (
    69        None,
    70        True, False,
    71        str(), 'text',
    72        tuple(), (0, 1, 2, 'n'),
    73        list(), [0, 1, 2, 'n'],
    74        set(), {0, 1, 2, 'n'},
    75        dict(), {'key': 'value'},
    76    ):
    77        with self.subTest(i=data):
    78            for operation in arithmetic:
    79                self.assertEqual(
    80                    arithmetic[operation](data, a_random_number),
    81                    'BOOM'
    82                )
    83            self.assertEqual(
    84                src.calculator.add(data, a_random_number()),
    85                error_message
    86            )
    

    the terminal shows AssertionError for every case in the iterable

    SUBFAILED(i=None) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=True) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=False) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i='') tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i='text') tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=()) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=(0, 1, 2, 'n')) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=[]) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=[0, 1, 2, 'n']) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i=set()) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i={0, 1, 2, 'n'}) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i={}) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    SUBFAILED(i={'key': 'value'}) tests/test_calculator.py::TestCalculator::test_calculator_sends_message_when_input_is_not_a_number - AssertionError: 'Excuse me?! Numbers only. Try again...' != 'BOOM'
    

    the test works

  • I change the expectation

    77            with self.subTest(i=data):
    78                for operation in arithmetic:
    79                    self.assertEqual(
    80                        arithmetic[operation](data, a_random_number),
    81                        error_message
    82                    )
    

    the test passes

  • I remove the other assertions

    58    def test_calculator_sends_message_when_input_is_not_a_number(self):
    59        error_message = 'Excuse me?! Numbers only. Try again...'
    60
    61        arithmetic = {
    62            'add': src.calculator.add,
    63            'subtract': src.calculator.subtract,
    64            'multiply': src.calculator.multiply,
    65            'divide': src.calculator.divide
    66        }
    67
    68        for data in (
    69            None,
    70            True, False,
    71            str(), 'text',
    72            tuple(), (0, 1, 2, 'n'),
    73            list(), [0, 1, 2, 'n'],
    74            set(), {0, 1, 2, 'n'},
    75            dict(), {'key': 'value'},
    76        ):
    77            with self.subTest(i=data):
    78                for operation in arithmetic:
    79                    self.assertEqual(
    80                        arithmetic[operation](data, a_random_number),
    81                        error_message
    82                    )
    83
    84    def test_calculator_w_list_items(self):
    

    this solution is not as easy to read as what was there before especially for someone new to Python


test_calculator_functions

I want to use a dictionary to write one test that covers all the 4 calculator functions

RED: make it pass

  • I add a new test

    173        with self.assertRaises(TypeError):
    174            src.calculator.subtract(*not_two_numbers)
    175
    176    def test_calculator_functions(self):
    177        arithmetic = {
    178            'addition': src.calculator.add,
    179            'subtraction': src.calculator.subtract,
    180            'division': src.calculator.divide,
    181            'multiplication': src.calculator.multiply
    182        }
    183
    184        for operation in arithmetic:
    185            with self.subTest(operation=operation):
    186                self.assertEqual(
    187                    arithmetic[operation](self.random_first_number, self.random_second_number),
    188                    'BOOM!!!'
    189                )
    190
    191
    192# Exceptions seen
    

    the terminal shows AssertionError for the 4 arithmetic operations

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

GREEN: make it pass

  • I need a way to add the calculations for each operation to the assertion. I add another dictionary

    181            'multiplication': src.calculator.multiply
    182        }
    183        data = {
    184            'addition': self.random_first_number+self.random_second_number,
    185            'subtraction': self.random_first_number-self.random_second_number,
    186            'division': self.random_first_number/self.random_second_number,
    187            'multiplication': self.random_first_number*self.random_second_number,
    188        }
    189
    190        for operation in arithmetic:
    
  • I use the new dictionary in the calculation in the assertion

    190        for operation in arithmetic:
    191            with self.subTest(operation=operation):
    192                self.assertEqual(
    193                    arithmetic[operation](self.random_first_number, self.random_second_number),
    194                    data[operation]
    195                )
    

    the test passes

REFACTOR: make it better

  • the two dictionaries in this test have the same keys. I put the dictionaries together

    187            'multiplication': self.random_first_number*self.random_second_number,
    188        }
    189
    190        arithmetic_tests = {
    191            'addition': {
    192                'function': src.calculator.add,
    193                'expectation': self.random_first_number+self.random_second_number,
    194            },
    195            'subtraction': {
    196                'function': src.calculator.subtract,
    197                'expectation': self.random_first_number-self.random_second_number,
    198            },
    199            'division': {
    200                'function': src.calculator.divide,
    201                'expectation': self.random_first_number/self.random_second_number,
    202            },
    203            'multiplication': {
    204                'function': src.calculator.multiply,
    205                'expectation': self.random_first_number*self.random_second_number,
    206            }
    207        }
    208
    209        for operation in arithmetic:
    
  • I add a new assertion in a for loop with the subTest method

    213                    data[operation]
    214                )
    215
    216        for operation in arithmetic_tests:
    217            with self.subTest(operation=operation):
    218                self.assertEqual(
    219                    arithmetic_tests[operation]['function'](
    220                        self.random_first_number,
    221                        self.random_second_number
    222                    ),
    223                    'BOOM!!!'
    224                )
    225
    226
    227def test_addition(self):
    

    the terminal shows AssertionError:

    SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: HIJ.KLMNOPQRSTUVW != 'BOOM!!!'
    SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: XYZA.BCDEFGHIJKLMN != 'BOOM!!!'
    SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: NOP.QRSTUVWXYZABCDEF != 'BOOM!!!'
    SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_functions - AssertionError: GHIJKLM.NOPQRSTUVWX != 'BOOM!!!'
    
  • I change the expectation

    216        for operation in arithmetic_tests:
    217            with self.subTest(operation=operation):
    218                self.assertEqual(
    219                    arithmetic_tests[operation]['function'](
    220                        self.random_first_number,
    221                        self.random_second_number
    222                    ),
    223                    arithmetic_tests[operation]['expectation']
    224                )
    

    the test passes

  • I remove the other dictionaries and for loop

    176    def test_calculator_functions(self):
    177        arithmetic_tests = {
    178            'addition': {
    179                'function': src.calculator.add,
    180                'expectation': self.random_first_number+self.random_second_number,
    181            },
    182            'subtraction': {
    183                'function': src.calculator.subtract,
    184                'expectation': self.random_first_number-self.random_second_number,
    185            },
    186            'division': {
    187                'function': src.calculator.divide,
    188                'expectation': self.random_first_number/self.random_second_number,
    189            },
    190            'multiplication': {
    191                'function': src.calculator.multiply,
    192                'expectation': self.random_first_number*self.random_second_number,
    193            }
    194        }
    195
    196        for operation in arithmetic_tests:
    197            with self.subTest(operation=operation):
    198                self.assertEqual(
    199                    arithmetic_tests[operation]['function'](
    200                        self.random_first_number,
    201                        self.random_second_number
    202                    ),
    203                    arithmetic_tests[operation]['expectation']
    204                )
    
  • I remove the test_addition, test_subtraction, test_multiplication

    class TestCalculator(unittest.TestCase):
    
        def setUp(self):
            self.random_first_number = a_random_number()
            self.random_second_number = a_random_number()
    
        def test_division(self):
    
  • I need a way to handle ZeroDivisionError in test_calculator_functions for the divide function. I change random_second_number to 0 in the setUp method to make ZeroDivisionError happen in the tests

    12    def setUp(self):
    13        self.random_first_number = a_random_number()
    14        # self.random_second_number = a_random_number()
    15        self.random_second_number = 0
    16
    17    def test_division(self):
    

    the terminal shows ZeroDivisionError for 3 tests

    FAILED tests/test_calculator.py::TestCalculator::test_calculator_functions - ZeroDivisionError: float division by zero
    FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - ZeroDivisionError: float division by zero
    FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - ZeroDivisionError: float division by zero
    
  • I use an exception handler to add a new class attribute (variable) for the result of division

    15        self.random_second_number = 0
    16        try:
    17            self.division_result = self.random_first_number / self.random_second_number
    18        except ZeroDivisionError:
    19            self.division_result = 'undefined: I cannot divide by 0'
    
  • I use the new class attribute (variable) in test_calculator_functions

    46            'division': {
    47                'function': src.calculator.divide,
    48                # 'expectation': self.random_first_number/self.random_second_number,
    49                'expectation': self.division_result,
    50            },
    

    the terminal shows ZeroDivisionError for 2 tests

    FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - ZeroDivisionError: float division by zero
    FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - ZeroDivisionError: float division by zero
    

    progress

  • I add the class attribute (variable) to test_calculator_w_list_items

    105        self.assertEqual(
    106            src.calculator.divide(two_numbers[-2], two_numbers[-1]),
    107            # self.random_first_number/self.random_second_number
    108            self.division_result
    109        )
    

    and

    122        self.assertEqual(
    123            src.calculator.divide(*two_numbers),
    124            # self.random_first_number/self.random_second_number
    125            self.division_result
    126        )
    

    the terminal shows ZeroDivisionError for 1 test

    FAILED tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - ZeroDivisionError: float division by zero
    
  • I add the class attribute (variable) to test_calculator_w_dictionary_items

    146        self.assertEqual(
    147            src.calculator.divide(two_numbers['first_input'], two_numbers['second_input']),
    148            # self.random_first_number/self.random_second_number
    149            self.division_result
    150        )
    

    and

    163        self.assertEqual(
    164            src.calculator.divide(**two_numbers),
    165            # self.random_first_number/self.random_second_number
    166            self.division_result
    167        )
    

    the terminal shows green. Lovely!

  • I remove the commented lines I added

  • I remove test_division

    def setUp(self):
        self.random_first_number = a_random_number()
        # self.random_second_number = a_random_number()
        self.random_second_number = 0
        try:
            self.division_result = self.random_first_number / self.random_second_number
        except ZeroDivisionError:
            self.division_result = 'undefined: I cannot divide by 0'
    
    def test_calculator_functions(self):
    
  • I change self.random_second_number back to a random float

    12def setUp(self):
    13    self.random_first_number = a_random_number()
    14    self.random_second_number = a_random_number()
    15    try:
    16        self.division_result = self.random_first_number / self.random_second_number
    17    except ZeroDivisionError:
    18        self.division_result = 'undefined: I cannot divide by 0'
    

    all tests are passing

  • the dictionaries in test_calculator_functions and test_calculator_sends_message_when_input_is_not_a_number are similar, I add a new dictionary in the setUp method

    17        except ZeroDivisionError:
    18            self.division_result = 'undefined: I cannot divide by 0'
    19
    20        self.arithmetic_tests = {
    21            'addition': {
    22                'function': src.calculator.add,
    23                'expectation': self.random_first_number+self.random_second_number,
    24            },
    25            'subtraction': {
    26                'function': src.calculator.subtract,
    27                'expectation': self.random_first_number-self.random_second_number,
    28            },
    29            'division': {
    30                'function': src.calculator.divide,
    31                'expectation': self.division_result,
    32            },
    33            'multiplication': {
    34                'function': src.calculator.multiply,
    35                'expectation': self.random_first_number*self.random_second_number,
    36            }
    37        }
    38
    39    def test_calculator_functions(self):
    
  • I use it in test_calculator_functions

    59        for operation in self.arithmetic_tests:
    60            with self.subTest(operation=operation):
    61                self.assertEqual(
    62                    self.arithmetic_tests[operation]['function'](
    63                        self.random_first_number,
    64                        self.random_second_number
    65                    ),
    66                    self.arithmetic_tests[operation]['expectation']
    67                )
    

    the test is green

  • I remove the arithemtic_tests dictionary from test_calculator_functions

    39    def test_calculator_functions(self):
    40        for operation in self.arithmetic_tests:
    41            with self.subTest(operation=operation):
    42                self.assertEqual(
    43                    self.arithmetic_tests[operation]['function'](
    44                        self.random_first_number,
    45                        self.random_second_number
    46                    ),
    47                    self.arithmetic_tests[operation]['expectation']
    48                )
    49
    50    def test_calculator_sends_message_when_input_is_not_a_number(self):
    

    still green

  • I use the new class attribute (variable) in test_calculator_sends_message_when_input_is_not_a_number

    69            with self.subTest(i=data):
    70                # for operation in arithmetic:
    71                for operation in self.arithmetic_tests:
    72                    self.assertEqual(
    73                        # arithmetic[operation](data, a_random_number),
    74                        self.arithmetic_tests[operation]['function'](data, a_random_number),
    75                        error_message
    76                    )
    

    the test is still green

  • I remove the commented lines and the arithmetic dictionary

    50    def test_calculator_sends_message_when_input_is_not_a_number(self):
    51        error_message = 'Excuse me?! Numbers only. Try again...'
    52
    53        for data in (
    54            None,
    55            True, False,
    56            str(), 'text',
    57            tuple(), (0, 1, 2, 'n'),
    58            list(), [0, 1, 2, 'n'],
    59            set(), {0, 1, 2, 'n'},
    60            dict(), {'key': 'value'},
    61        ):
    62            with self.subTest(i=data):
    63                for operation in self.arithmetic_tests:
    64                    self.assertEqual(
    65                        self.arithmetic_tests[operation]['function'](data, a_random_number),
    66                        error_message
    67                    )
    68
    69    def test_calculator_w_list_items(self):
    

    still green

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

    158        with self.assertRaises(TypeError):
    159            src.calculator.subtract(*not_two_numbers)
    160
    161        for operation in self.arithmetic_tests:
    162            with self.subTest(operation=operation):
    163                self.arithmetic_tests[operation]['function'](*not_two_numbers)
    164
    165
    166# Exceptions seen
    

    the terminal shows TypeError for all 4 cases

    SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ...
    SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ...
    SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ...
    SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_raises_type_error_when_given_more_than_two_inputs - TypeError: only_takes_numbers.<locals>.wrapper() takes 2 positional arguments but 3 were ...
    
  • I add the assertRaises method

    161        for operation in self.arithmetic_tests:
    162            with self.subTest(operation=operation):
    163                with self.assertRaises(TypeError):
    164                    self.arithmetic_tests[operation]['function'](*not_two_numbers)
    

    the test passes

  • I remove the other assertions

    149    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    150        not_two_numbers = [0, 1, 2]
    151
    152        for operation in self.arithmetic_tests:
    153            with self.subTest(operation=operation):
    154                with self.assertRaises(TypeError):
    155                    self.arithmetic_tests[operation]['function'](*not_two_numbers)
    156
    157
    158# Exceptions seen
    

    still green

  • I no longer need the variable, I remove it and use the list directly in the assertion

    149    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    150        for operation in self.arithmetic_tests:
    151            with self.subTest(operation=operation):
    152                with self.assertRaises(TypeError):
    153                    self.arithmetic_tests[operation]['function'](*[0, 1, 2])
    

    the test is still green

  • Python allows me to put the 2 with contexts together in one statement

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

    green

  • I remove the commented lines

    137    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    138        for operation in self.arithmetic_tests:
    139            with (
    140                self.subTest(operation=operation),
    141                self.assertRaises(TypeError),
    142            ):
    143                self.arithmetic_tests[operation]['function'](*[0, 1, 2])
    
  • I add a for loop to test_calculator_w_list_items

    105        self.assertEqual(
    106            src.calculator.subtract(*two_numbers),
    107            self.random_first_number-self.random_second_number
    108        )
    109
    110        for operation in self.arithmetic_tests:
    111            with self.subTest(operation=operation):
    112                self.assertEqual(
    113                    self.arithmetic_tests[operation]['function'](
    114                        *two_numbers
    115                    ),
    116                    'BOOM!!!'
    117                )
    118
    119    def test_calculator_w_dictionary_items(self):
    

    the terminal shows AssertionError for the 4 operations

    SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: YZA.BCDEFGHIJKLMNO != 'BOOM!!!'
    SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: PQR.STUVWXYZABCDE != 'BOOM!!!'
    SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: F.GHIJKLMNOPQRSTUV != 'BOOM!!!'
    SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_w_list_items - AssertionError: WXYABC.DEFGHIJKLMN != 'BOOM!!!'
    
  • I change the expectation

    1        for operation in self.arithmetic_tests:
    2            with self.subTest(operation=operation):
    3                self.assertEqual(
    4                    self.arithmetic_tests[operation]['function'](
    5                        *two_numbers
    6                    ),
    7                    self.arithmetic_tests[operation]['expectation']
    8                )
    

    the test passes

  • I remove all the assertions for the starred expression

     89        self.assertEqual(
     90            src.calculator.subtract(two_numbers[-2], two_numbers[0]),
     91            self.random_first_number-self.random_first_number
     92        )
     93
     94        for operation in self.arithmetic_tests:
     95            with self.subTest(operation=operation):
     96                self.assertEqual(
     97                    self.arithmetic_tests[operation]['function'](
     98                        *two_numbers
     99                    ),
    100                    self.arithmetic_tests[operation]['expectation']
    101                )
    102
    103    def test_calculator_w_dictionary_items(self):
    

    still green

  • I add a for loop to test_calculator_w_dictionary_items

    137        self.assertEqual(
    138            src.calculator.subtract(**two_numbers),
    139            self.random_first_number-self.random_second_number
    140        )
    141
    142        for operation in self.arithmetic_tests:
    143            with self.subTest(operation=operation):
    144                self.assertEqual(
    145                    self.arithmetic_tests[operation]['function'](
    146                        **two_numbers
    147                    ),
    148                    'BOOM!!!'
    149                )
    150
    151    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    the terminal shows AssertionError for the 4 operations

    SUBFAILED(operation='addition') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: OPQ.RSTUVWXYZABCDEF != 'BOOM!!!'
    SUBFAILED(operation='subtraction') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: GHI.JKLMNOPQRSTUVWX != 'BOOM!!!'
    SUBFAILED(operation='division') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: Y.ZABCDEFGHIJKLMNOP != 'BOOM!!!'
    SUBFAILED(operation='multiplication') tests/test_calculator.py::TestCalculator::test_calculator_w_dictionary_items - AssertionError: -QRSTU.VWXYZABCDEF != 'BOOM!!!'
    
  • I change the expectation

    142        for operation in self.arithmetic_tests:
    143            with self.subTest(operation=operation):
    144                self.assertEqual(
    145                    self.arithmetic_tests[operation]['function'](
    146                        **two_numbers
    147                    ),
    148                    self.arithmetic_tests[operation]['expectation']
    149                )
    

    the test passes

  • I remove the other assertions for the starred expression

    121        self.assertEqual(
    122            src.calculator.subtract(two_numbers['first_input'], two_numbers['first_input']),
    123            self.random_first_number-self.random_first_number
    124        )
    125
    126        for operation in self.arithmetic_tests:
    127            with self.subTest(operation=operation):
    128                self.assertEqual(
    129                    self.arithmetic_tests[operation]['function'](
    130                        **two_numbers
    131                    ),
    132                    self.arithmetic_tests[operation]['expectation']
    133                )
    134
    135    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    

    still green


close the project

  • I close test_calculator.py in the editor

  • I click in the terminal and exit the tests with ctrl+c on the keyboard, 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 the following 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

Would you like to put it all together in Classes?


rate pumping python

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