how to make a calculator 5

I want to practice using lists with the calculator project


preview

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


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 5 items
    
    tests/test_calculator.py .....                                [100%]
    
    ======================== 5 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_sends_message_when_input_is_a_list

I want to see what happens when I send a list as input to the calculator program, will it send a message or raise TypeError?


RED: make it fail


I add a test to see what happens when I send a list as input

 88        self.assertEqual(
 89            src.calculator.subtract('1', '1'),
 90            error_message
 91        )
 92
 93    def test_calculator_sends_message_when_input_is_a_list(self):
 94        a_list = [0, 1, 2, 3]
 95
 96        self.assertEqual(
 97            src.calculator.add(a_list, 0),
 98            'BOOM!!!'
 99        )
100
101
102# Exceptions seen

the terminal shows AssertionError

AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'

GREEN: make it pass


I change the expectation to match

96        self.assertEqual(
97            src.calculator.add(a_list, 0),
98            'brmph?! Numbers only. Try again...'
99        )

the test passes


REFACTOR: make it better


  • I add another assertion for the next function

     93    def test_calculator_sends_message_when_input_is_a_list(self):
     94        a_list = [0, 1, 2, 3]
     95
     96        self.assertEqual(
     97            src.calculator.add(a_list, 0),
     98            'brmph?! Numbers only. Try again...'
     99        )
    100        self.assertEqual(
    101            src.calculator.divide(a_list, 1),
    102            'BAP!!!'
    103        )
    104
    105
    106# Exceptions seen
    

    the terminal shows AssertionError

    AssertionError: 'brmph?! Numbers only. Try again...' != 'BAP!!!'
    
  • I change the expectation

    101        self.assertEqual(
    102            src.calculator.divide(a_list, 1),
    103            'brmph?! Numbers only. Try again...'
    104        )
    

    the test passes. Wait a minute! I just wrote the same thing twice, and I did it 8 times before in test_calculator_sends_message_when_input_is_not_a_number and 2 times in the numbers_only function. Never again

  • I add a variable

     93    def test_calculator_sends_message_when_input_is_a_list(self):
     94        a_list = [0, 1, 2, 3]
     95        error_message = 'brmph?! Numbers only. Try again...'
     96
     97        self.assertEqual(
     98            src.calculator.add(a_list, 0),
     99            'brmph?! Numbers only. Try again...'
    100        )
    
  • I use the variable to remove the repetition

     93    def test_calculator_sends_message_when_input_is_a_list(self):
     94        a_list = [0, 1, 2, 3]
     95        error_message = 'brmph?! Numbers only. Try again...'
     96
     97        self.assertEqual(
     98            src.calculator.add(a_list, 0),
     99            error_message
    100        )
    101        self.assertEqual(
    102            src.calculator.divide(a_list, 1),
    103            error_message
    104        )
    

    the test is still green


how to multiply a list

  • I add an assertion for the multiply function

    101        self.assertEqual(
    102            src.calculator.divide(a_list, 1),
    103            error_message
    104        )
    105        self.assertEqual(
    106            src.calculator.multiply(a_list, 2),
    107            'BOOM!!!'
    108        )
    109
    110
    111# Exceptions seen
    

    the terminal shows AssertionError

    AssertionError: [0, 1, 2, 3, 0, 1, 2, 3] != 'BOOM!!!'
    

    oh wow! I now know how to multiply a list

  • I change the expectation of the test to the error message

    105        self.assertEqual(
    106            src.calculator.multiply(a_list, 2),
    107            error_message
    108        )
    

    the terminal shows AssertionError

    AssertionError: [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] != 'brmph?! Numbers only. Try again...'
    
  • I open calculator.py in the editor

  • I add an if statement to the multiply function in calculator.py

    19@numbers_only
    20def multiply(first_input, second_input):
    21    if (
    22        isinstance(first_input, list)
    23        or
    24        isinstance(second_input, list)
    25    ):
    26        return 'brmph?! Numbers only. Try again...'
    27    return first_input * second_input
    

    the test passes

  • I add an assertion for the subtract function in test_calculator_sends_message_when_input_is_a_list to test_calculator.py

    105        self.assertEqual(
    106            src.calculator.multiply(a_list, 2),
    107            error_message
    108        )
    109        self.assertEqual(
    110            src.calculator.subtract(a_list, 3),
    111            'BOOM!!!'
    112        )
    113
    114
    115# Exceptions seen
    

    the terminal shows AssertionError

    AssertionError: 'brmph?! Numbers only. Try again...' != 'BOOM!!!'
    
  • I change the expectation to match

     93    def test_calculator_sends_message_when_input_is_a_list(self):
     94        a_list = [0, 1, 2, 3]
     95        error_message = 'brmph?! Numbers only. Try again...'
     96
     97        self.assertEqual(
     98            src.calculator.add(a_list, 0),
     99            error_message
    100        )
    101        self.assertEqual(
    102            src.calculator.divide(a_list, 1),
    103            error_message
    104        )
    105        self.assertEqual(
    106            src.calculator.multiply(a_list, 2),
    107            error_message
    108        )
    109        self.assertEqual(
    110            src.calculator.subtract(a_list, 3),
    111            error_message
    112        )
    113
    114
    115# Exceptions seen
    

    the test passes

  • I remove the name of the test to move the new assertions to test_calculator_sends_message_when_input_is_not_a_number

     84        self.assertEqual(
     85            src.calculator.multiply('1', '1'),
     86            error_message
     87        )
     88        self.assertEqual(
     89            src.calculator.subtract('1', '1'),
     90            error_message
     91        )
     92
     93        a_list = [0, 1, 2, 3]
     94        error_message = 'brmph?! Numbers only. Try again...'
     95
     96        self.assertEqual(
     97            src.calculator.add(a_list, 0),
     98            error_message
     99        )
    100        self.assertEqual(
    101            src.calculator.divide(a_list, 1),
    102            error_message
    103        )
    

    the tests are still green

  • I remove the repetition of the error_message variable

     57    def test_calculator_sends_message_when_input_is_not_a_number(self):
     58        error_message = 'brmph?! Numbers only. Try again...'
     59
     60        self.assertEqual(
     61            src.calculator.add(None, None),
     62            error_message
     63        )
     64        self.assertEqual(
     65            src.calculator.divide(None, None),
     66            error_message
     67        )
     68        self.assertEqual(
     69            src.calculator.multiply(None, None),
     70            error_message
     71        )
     72        self.assertEqual(
     73            src.calculator.subtract(None, None),
     74            error_message
     75        )
     76        self.assertEqual(
     77            src.calculator.add('1', '1'),
     78            error_message
     79        )
     80        self.assertEqual(
     81            src.calculator.divide('1', '1'),
     82            error_message
     83        )
     84        self.assertEqual(
     85            src.calculator.multiply('1', '1'),
     86            error_message
     87        )
     88        self.assertEqual(
     89            src.calculator.subtract('1', '1'),
     90            error_message
     91        )
     92
     93        a_list = [0, 1, 2, 3]
     94
     95        self.assertEqual(
     96            src.calculator.add(a_list, 0),
     97            error_message
     98        )
     99        self.assertEqual(
    100            src.calculator.divide(a_list, 1),
    101            error_message
    102        )
    103        self.assertEqual(
    104            src.calculator.multiply(a_list, 2),
    105            error_message
    106        )
    107        self.assertEqual(
    108            src.calculator.subtract(a_list, 3),
    109            error_message
    110        )
    111
    112
    113# Exceptions seen
    

    still green. test_calculator_sends_message_when_input_is_not_a_number is getting long, there has to be a better way to test the calculator with inputs that are NOT numbers


test_calculator_w_list_items

I can use a list to test the calculator functions as long as its items are numbers


RED: make it fail


I add a new test to use the index of the items in the list to test the calculator

107          self.assertEqual(
108              src.calculator.subtract(a_list, 3),
109              error_message
110          )
111
112      def test_calculator_w_list_items(self):
113          two_numbers = [
114              self.random_first_number,
115              self.random_second_number
116          ]
117
118          self.assertEqual(
119              src.calculator.add(
120                  two_numbers[0],
121                  two_numbers[1]
122              ),
123              self.random_first_number-self.random_second_number
124          )
125
126
127  # Exceptions seen

the terminal shows AssertionError

AssertionError: ABC.DEFGHIJKLMNOPQ != RST.UVWXYZABCDEFG

GREEN: make it pass


I change the expectation to the right calculation

118          self.assertEqual(
119              src.calculator.add(
120                  two_numbers[0],
121                  two_numbers[1]
122              ),
123              self.random_first_number+self.random_second_number
124          )

the test passes. two_numbers is a list with two items - self.random_first_number and self.random_second_number, this means

  • two_numbers[0] is self.random_first_number

  • two_numbers[1] is self.random_second_number


REFACTOR: make it better


  • I add an assertion for the divide function

    118        self.assertEqual(
    119            src.calculator.add(
    120                two_numbers[0],
    121                two_numbers[1]
    122            ),
    123            self.random_first_number+self.random_second_number
    124        )
    125        self.assertEqual(
    126            src.calculator.divide(
    127                two_numbers[-2],
    128                two_numbers[-1]
    129            ),
    130            self.random_first_number*self.random_second_number
    131        )
    

    the terminal shows AssertionError

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

    125        self.assertEqual(
    126            src.calculator.divide(
    127                two_numbers[-2],
    128                two_numbers[-1]
    129            ),
    130            self.random_first_number/self.random_second_number
    131        )
    

    the test passes. two_numbers is a list with two items - self.random_first_number and self.random_second_number, this means

  • two_numbers[-2] is self.random_first_number

  • two_numbers[-1] is self.random_second_number

  • I add an assertion for multiplication

    125        self.assertEqual(
    126            src.calculator.divide(
    127                two_numbers[-2],
    128                two_numbers[-1]
    129            ),
    130            self.random_first_number/self.random_second_number
    131        )
    132        self.assertEqual(
    133            src.calculator.multiply(
    134                two_numbers[1],
    135                two_numbers[-1]
    136            ),
    137            self.random_first_number*self.random_second_number
    138        )
    

    the terminal shows AssertionError

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

    132        self.assertEqual(
    133            src.calculator.multiply(
    134                two_numbers[1],
    135                two_numbers[-1]
    136            ),
    137            self.random_second_number*self.random_second_number
    138        )
    

    the test passes. two_numbers is a list with two items - self.random_first_number and self.random_second_number, this means

  • two_numbers[1] is self.random_second_number

  • two_numbers[-1] is self.random_second_number

  • I add an assertion for the subtract function

    132        self.assertEqual(
    133            src.calculator.multiply(
    134                two_numbers[1],
    135                two_numbers[-1]
    136            ),
    137            self.random_second_number*self.random_second_number
    138        )
    139        self.assertEqual(
    140            src.calculator.subtract(
    141                two_numbers[-2],
    142                two_numbers[0]
    143            ),
    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

    139        self.assertEqual(
    140            src.calculator.subtract(
    141                two_numbers[-2],
    142                two_numbers[0]
    143            ),
    144            self.random_first_number-self.random_first_number
    145        )
    146
    147
    148# Exceptions seen
    

    the test passes. two_numbers is a list with two items - self.random_first_number and self.random_second_number, this means

  • two_numbers[-2] is self.random_first_number

  • two_numbers[0] is self.random_first_number


test calculator with * expression


RED: make it fail


I can use a starred expression to unpack the list in an assertion like the positional arguments in test_functions_w_unknown_arguments

139      self.assertEqual(
140          src.calculator.subtract(
141              two_numbers[-2],
142              two_numbers[0]
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      )

the terminal shows AssertionError

AssertionError: GHI.JKLMNOPQRSTUVW != XYZ.ABCDEFGHIJKLMN

GREEN: make it pass


I change the expectation to match the result of adding the two numbers from the list

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

the test passes. The starred expression gives the items of the list in the same order every time, this means these statements are the same

src.calculator.add(*two_numbers)
src.calculator.add(self.random_first_number, self.random_second_number)

because two_numbers is a list with two items - self.random_first_number and self.random_second_number


REFACTOR: make it better


  • I add another assertion for division

    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 in the expectation to multiplication

    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 to subtraction

    112    def test_calculator_w_list_items(self):
    113        two_numbers = [
    114            self.random_first_number,
    115            self.random_second_number
    116        ]
    117
    118        self.assertEqual(
    119            src.calculator.add(
    120                two_numbers[0],
    121                two_numbers[1]
    122            ),
    123            self.random_first_number+self.random_second_number
    124        )
    125        self.assertEqual(
    126            src.calculator.divide(
    127                two_numbers[-2],
    128                two_numbers[-1]
    129            ),
    130            self.random_first_number/self.random_second_number
    131        )
    132        self.assertEqual(
    133            src.calculator.multiply(
    134                two_numbers[1],
    135                two_numbers[-1]
    136            ),
    137            self.random_second_number*self.random_second_number
    138        )
    139        self.assertEqual(
    140            src.calculator.subtract(
    141                two_numbers[-2],
    142                two_numbers[0]
    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        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        )
    158        self.assertEqual(
    159            src.calculator.subtract(*two_numbers),
    160            self.random_first_number-self.random_second_number
    161        )
    162
    163
    164# Exceptions seen
    

    the test passes


test_calculator_raises_type_error_when_given_more_than_two_inputs

It is important to remember that the starred expression always gives the items from the list in order, and I cannot use a list that has more than 2 numbers with these calculator functions since they only take 2 inputs


RED: make it fail


I add a new test to show the problem when I have more than 2 inputs and use a starred expression

158        self.assertEqual(
159            src.calculator.subtract(*two_numbers),
160            self.random_first_number-self.random_second_number
161        )
162
163    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
164        not_two_numbers = [0, 1, 2]
165
166        src.calculator.add(*not_two_numbers)
167
168
169# Exceptions seen

the terminal shows TypeError

TypeError: numbers_only.<locals>.wrapper() takes 2 positional arguments but 3 were given

GREEN: make it pass


I add the assertRaises method to handle the Exception

163    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
164        not_two_numbers = [0, 1, 2]
165
166        with self.assertRaises(TypeError):
167            src.calculator.add(*not_two_numbers)

the test passes


REFACTOR: make it better


  • I add a failing line for division with the new list

    163    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    164        not_two_numbers = [0, 1, 2]
    165
    166        with self.assertRaises(TypeError):
    167            src.calculator.add(*not_two_numbers)
    168        src.calculator.divide(*not_two_numbers)
    

    the terminal shows TypeError

    TypeError: numbers_only.<locals>.wrapper() takes 2 positional arguments but 3 were given
    
  • I add assertRaises

    166        with self.assertRaises(TypeError):
    167            src.calculator.add(*not_two_numbers)
    168        with self.assertRaises(TypeError):
    169            src.calculator.divide(*not_two_numbers)
    

    the test passes

  • I add a line for multiplication

    168        with self.assertRaises(TypeError):
    169            src.calculator.divide(*not_two_numbers)
    170        src.calculator.multiply(*not_two_numbers)
    

    the terminal shows TypeError

    TypeError: numbers_only.<locals>.wrapper() takes 2 positional arguments but 3 were given
    
  • I add assertRaises

    168        with self.assertRaises(TypeError):
    169            src.calculator.divide(*not_two_numbers)
    170        with self.assertRaises(TypeError):
    171            src.calculator.multiply(*not_two_numbers)
    

    the test passes

  • I add the last line

    170        with self.assertRaises(TypeError):
    171            src.calculator.multiply(*not_two_numbers)
    172        src.calculator.subtract(*not_two_numbers)
    

    the terminal shows TypeError

    TypeError: numbers_only.<locals>.wrapper() takes 2 positional arguments but 3 were given
    
  • I handle the Exception

    163    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    164        not_two_numbers = [0, 1, 2]
    165
    166        with self.assertRaises(TypeError):
    167            src.calculator.add(*not_two_numbers)
    168        with self.assertRaises(TypeError):
    169            src.calculator.divide(*not_two_numbers)
    170        with self.assertRaises(TypeError):
    171            src.calculator.multiply(*not_two_numbers)
    172        with self.assertRaises(TypeError):
    173            src.calculator.subtract(*not_two_numbers)
    174
    175
    176# Exceptions seen
    

    the test passes


close the project

  • I close test_calculator.py and calculator.py in the editors

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

  • I change directory to the parent of calculator

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

I added these tests to the calculator program after testing lists


code from the chapter

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


what is next?

you know

would you like to test list comprehensions? They are a quick way to make lists


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