lists


what is a list?

A list is a container object that can hold any objects

  • they are represented with []

  • they can be made with the list constructor (list())

  • they can be changed with an operation, which means they are mutable

preview

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

  1import unittest
  2
  3
  4class TestLists(unittest.TestCase):
  5
  6    def test_making_a_list(self):
  7        self.assertEqual(list(), [])
  8        self.assertEqual(list((0, 1, 2, 'n')), [0, 1, 2, 'n'])
  9
 10    def test_attributes_and_methods_of_lists(self):
 11        self.maxDiff = None
 12        self.assertEqual(
 13            dir(list),
 14            [
 15                '__add__',
 16                '__class__',
 17                '__class_getitem__',
 18                '__contains__',
 19                '__delattr__',
 20                '__delitem__',
 21                '__dir__',
 22                '__doc__',
 23                '__eq__',
 24                '__format__',
 25                '__ge__',
 26                '__getattribute__',
 27                '__getitem__',
 28                '__getstate__',
 29                '__gt__',
 30                '__hash__',
 31                '__iadd__',
 32                '__imul__',
 33                '__init__',
 34                '__init_subclass__',
 35                '__iter__',
 36                '__le__',
 37                '__len__',
 38                '__lt__',
 39                '__mul__',
 40                '__ne__',
 41                '__new__',
 42                '__reduce__',
 43                '__reduce_ex__',
 44                '__repr__',
 45                '__reversed__',
 46                '__rmul__',
 47                '__setattr__',
 48                '__setitem__',
 49                '__sizeof__',
 50                '__str__',
 51                '__subclasshook__',
 52                'append',
 53                'clear',
 54                'copy',
 55                'count',
 56                'extend',
 57                'index',
 58                'insert',
 59                'pop',
 60                'remove',
 61                'reverse',
 62                'sort',
 63            ]
 64        )
 65
 66    def test_append_adds_item_to_end_of_a_list(self):
 67        a_list = [0, 1, 2, 'n']
 68        self.assertIsNone(a_list.append('n+1'))
 69        self.assertEqual(a_list, [0, 1, 2, 'n', 'n+1'])
 70
 71    def test_clear_empties_a_list(self):
 72        a_list = [0, 1, 2, 'n']
 73        self.assertIsNone(a_list.clear())
 74        self.assertEqual(a_list, [])
 75
 76    def test_copy_a_list(self):
 77        a_list = [0, 1, 2, 'n']
 78        self.assertEqual(a_list.copy(), [0, 1, 2, 'n'])
 79
 80    def test_count_number_of_times_item_is_in_a_list(self):
 81        a_list = [0, 1, 2, 1, 'n', 1]
 82        self.assertEqual(a_list.count(0), 1)
 83        self.assertEqual(a_list.count(1), 3)
 84        self.assertEqual(a_list.count('not in list'), 0)
 85
 86    def test_extend_adds_items_from_an_iterable_to_end_of_a_list(self):
 87        a_list = [0, 1, 2, 'n']
 88        self.assertIsNone(a_list.extend((2, 1, 0)))
 89        self.assertEqual(a_list, [0, 1, 2, 'n', 2, 1, 0])
 90
 91    def test_index_returns_first_position_of_item_in_a_list(self):
 92        a_list = ['1st', '2nd', '3rd', '...last', '1st']
 93        self.assertEqual(a_list.index('1st', 0), 0)
 94        self.assertEqual(a_list.index('3rd', 0), 2)
 95        self.assertEqual(a_list.index('2nd', 0), 1)
 96        self.assertEqual(a_list.index('...last', 0), 3)
 97        self.assertEqual(a_list.index('1st', 1), 4)
 98
 99        with self.assertRaises(ValueError):
100            a_list.index('not in list')
101
102    def test_insert_item_at_position_in_a_list(self):
103        a_list = [0, 1, 2, 'n']
104        self.assertIsNone(a_list.insert(0, -1))
105        self.assertEqual(a_list, [-1, 0, 1, 2, 'n'])
106        self.assertIsNone(a_list.insert(3, 1.5))
107        self.assertEqual(a_list, [-1, 0, 1, 1.5, 2, 'n'])
108
109    def test_pop_removes_and_returns_last_item_from_a_list(self):
110        a_list = [0, 1, 2, 'n']
111        self.assertEqual(a_list.pop(), 'n')
112        self.assertEqual(a_list, [0, 1, 2])
113        self.assertEqual(a_list.pop(), 2)
114        self.assertEqual(a_list, [0, 1])
115
116    def test_remove_first_time_item_is_in_a_list(self):
117        a_list = [0, 1, 0, 2, 0, 'n']
118        self.assertIsNone(a_list.remove(0))
119        self.assertEqual(a_list, [1, 0, 2, 0, 'n'])
120
121        with self.assertRaises(ValueError):
122            a_list.remove('not in list')
123
124    def test_reverse_a_list(self):
125        a_list = [0, 1, 2, 'n']
126        self.assertIsNone(a_list.reverse())
127        self.assertEqual(a_list, ['n', 2, 1, 0])
128
129    def test_sort_a_list(self):
130        with self.assertRaises(TypeError):
131            [0, 1, 2, 'n'].sort()
132
133        a_list = [0, 1, -1, 2, -2, 3, -3]
134        self.assertIsNone(a_list.sort())
135        self.assertEqual(a_list, [-3, -2, -1, 0, 1, 2, 3])
136
137    def test_getting_items_of_a_list(self):
138        a_list = ['1st', '2nd', '3rd', '...last']
139        self.assertEqual(a_list[0], '1st')
140        self.assertEqual(a_list[a_list.index('1st')], '1st')
141        self.assertEqual(a_list[-4], '1st')
142        self.assertEqual(a_list[2], '3rd')
143        self.assertEqual(a_list[-2], '3rd')
144        self.assertEqual(a_list[1], '2nd')
145        self.assertEqual(a_list[-3], '2nd')
146        self.assertEqual(a_list[3], '...last')
147        self.assertEqual(a_list[-1], '...last')
148
149    def test_setting_items_in_a_list(self):
150        a_list = ['1st', '2nd', '3rd', '...last']
151        a_list[-1] = '4th'
152        self.assertEqual(a_list, ['1st', '2nd', '3rd', '4th'])
153
154    def test_looking_at_parts_of_a_list_aka_slicing(self):
155        a_list = ['a', 'b', 'c', 'd']
156        self.assertEqual(a_list[0:2], ['a', 'b'])
157        self.assertEqual(a_list[:2], ['a', 'b'])
158        self.assertEqual(a_list[1:4], ['b', 'c', 'd'])
159        self.assertEqual(a_list[1:], ['b', 'c', 'd'])
160        self.assertEqual(a_list[0:3], ['a', 'b', 'c'])
161        self.assertEqual(a_list[1:3], ['b', 'c'])
162        self.assertEqual(a_list[:], a_list.copy())
163
164    def test_index_error(self):
165        a_list = ['a', 'b', 'c', 'd']
166
167        with self.assertRaises(IndexError):
168            a_list[4]
169        with self.assertRaises(IndexError):
170            a_list[-5]
171        with self.assertRaises(IndexError):
172            [].pop()
173        with self.assertRaises(IndexError):
174            [][-1]
175
176
177# Exceptions seen
178# AssertionError
179# TypeError
180# ValueError
181# IndexError

questions about lists

Here are questions you can answer after going through this chapter


start the project

  • I name this project lists

  • I open makePythonTdd.sh or makePythonTdd.ps1 in the editor

    Tip

    Here is a quick way to open makePythonTdd.sh or makePythonTdd.ps1 if you are using Visual Studio Code

    code makePythonTdd.sh
    

    on Windows without Windows Subsystem for Linux use

    code makePythonTdd.ps1
    
  • I change everywhere I have type_error to the name of this project in makePythonTdd.sh

     1#!/bin/bash
     2mkdir lists
     3cd lists
     4mkdir src
     5touch src/lists.py
     6mkdir tests
     7touch tests/__init__.py
     8
     9echo "import unittest
    10
    11
    12class TestLists(unittest.TestCase):
    13
    14    def test_failure(self):
    15        self.assertFalse(True)
    16
    17
    18# Exceptions seen
    19# AssertionError
    20" > tests/test_lists.py
    

    Attention

    on Windows without Windows Subsystem for Linux use makePythonTdd.ps1 instead of makePythonTdd.sh

     1mkdir lists
     2cd lists
     3mkdir src
     4New-Item src/lists.py
     5mkdir tests
     6New-Item tests/__init__.py
     7
     8"import unittest
     9
    10
    11class TestLists(unittest.TestCase):
    12
    13    def test_failure(self):
    14        self.assertFalse(True)
    15
    16# Exceptions seen
    17# AssertionError
    18" | Out-File tests/test_lists.py
    
  • I run the program in the terminal

    ./makePythonTdd.sh
    

    Attention

    on Windows without Windows Subsystem for Linux use makePythonTdd.ps1 instead of makePythonTdd.sh

    ./makePythonTdd.ps1
    

    the terminal shows AssertionError

    ======================================= FAILURES =======================================
    ________________________________ TestLists.test_failure ________________________________
    
    self = <tests.test_lists.TestLists testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_lists.py:7: AssertionError
    =============================== short test summary info ================================
    FAILED tests/test_lists.py::TestLists::test_failure - AssertionError: True is not false
    ================================== 1 failed in X.YZs ===================================
    
  • I hold ctrl (Windows/Linux) or option or command (MacOS) on the keyboard and use the mouse to click on tests/test_lists.py:7 to open it in the editor

  • then I change True to False

    7        self.assertFalse(False)
    

    the test passes


test_making_a_list

RED: make it fail

I change test_failure to test_making_a_list

4class TestLists(unittest.TestCase):
5
6    def test_making_a_list(self):
7        self.assertEqual(list(), None)

the terminal shows AssertionError

AssertionError: [] != None

GREEN: make it pass

I change the expectation

7        self.assertEqual(list(), [])

the test passes. I can make an empty list with list() or []

REFACTOR: make it better

  • I add another assertion, this time with input to the list constructor, I want to make a list that has things in it

    7        self.assertEqual(list(), [])
    8        self.assertEqual(list(0), [])
    

    the terminal shows TypeError

    TypeError: 'int' object is not iterable
    
  • I add TypeError to the list of Exceptions seen

    11# Exceptions seen
    12# AssertionError
    13# TypeError
    

what is an iterable?

An iterable is an object that can return what it has, one at a time. In order words, I can loop over it. strings, tuples, lists, sets and dictionaries are iterable

  • I change the input to a tuple (anything in parentheses (()), separated by a comma)

    7        self.assertEqual(list(), [])
    8        self.assertEqual(list((0, 1, 2, 'n')), [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 'n'] != []
    
  • I change the expectation to match

    8        self.assertEqual(list((0, 1, 2, 'n')), [0, 1, 2, 'n'])
    

    the test passes.

I can make a list with the constructor (list()) or square brackets([]) which uses less characters


test_attributes_and_methods_of_lists

how to see the the attributes and methods of an object

I want to test the things I can do with lists. I can use the dir function to see the attributes and methods of objects, it is part of Python’s Built-in Functions

RED: make it fail

I add a failing test

 8        self.assertEqual(list(0, 1, 2, 'n'), [0, 1, 2, 'n'])
 9
10    def test_attributes_and_methods_of_lists(self):
11        self.assertEqual(
12            dir(list),
13            []
14        )

the terminal shows AssertionError

AssertionError: Lists differ: ['__add__', '__class__', '__class_getitem_[552 chars]ort'] != []

there is also a note on how to see the full difference between dir(list) and my empty list

Diff is 748 characters long. Set self.maxDiff to None to see it

maxDiff is an attribute of the unittest.TestCase class that sets the maximum number of characters to show when comparing 2 objects in the terminal, when it is set to None it shows the full difference

GREEN: make it pass

  • I add self.maxDiff to the test then move the terminal to the right

    10    def test_attributes_and_methods_of_lists(self):
    11        self.maxDiff = None
    12        self.assertEqual(
    13            dir(list),
    14            []
    15        )
    

    the terminal shows a long list of items. I copy (ctrl/command+c) and paste (ctrl/command+v) them from the terminal then use find and replace to remove the extra characters E and -

    Note

    results can be different because of the Python version

    10    def test_attributes_and_methods_of_lists(self):
    11            self.maxDiff = None
    12            self.assertEqual(
    13                dir(list),
    14                [
    15                    '__add__',
    16                    '__class__',
    17                    '__class_getitem__',
    18                    '__contains__',
    19                    '__delattr__',
    20                    '__delitem__',
    21                    '__dir__',
    22                    '__doc__',
    23                    '__eq__',
    24                    '__format__',
    25                    '__ge__',
    26                    '__getattribute__',
    27                    '__getitem__',
    28                    '__getstate__',
    29                    '__gt__',
    30                    '__hash__',
    31                    '__iadd__',
    32                    '__imul__',
    33                    '__init__',
    34                    '__init_subclass__',
    35                    '__iter__',
    36                    '__le__',
    37                    '__len__',
    38                    '__lt__',
    39                    '__mul__',
    40                    '__ne__',
    41                    '__new__',
    42                    '__reduce__',
    43                    '__reduce_ex__',
    44                    '__repr__',
    45                    '__reversed__',
    46                    '__rmul__',
    47                    '__setattr__',
    48                    '__setitem__',
    49                    '__sizeof__',
    50                    '__str__',
    51                    '__subclasshook__',
    52                    'append',
    53                    'clear',
    54                    'copy',
    55                    'count',
    56                    'extend',
    57                    'index',
    58                    'insert',
    59                    'pop',
    60                    'remove',
    61                    'reverse',
    62                    'sort'
    63                ]
    64            )
    

    the test passes and I move the terminal back to the bottom

  • I copy (ctrl/command+c) and paste (ctrl/command+v) the names that do NOT have double underscores (__) to use as a TODO list

    67'append',
    68'clear',
    69'copy',
    70'count',
    71'extend',
    72'index',
    73'insert',
    74'pop',
    75'remove',
    76'reverse',
    77'sort'
    78
    79
    80# Exceptions seen
    81# AssertionError
    82# TypeError
    

test_append_adds_item_to_end_of_a_list

RED: make it fail

I add a test for the append method

62                  'sort'
63              ]
64          )
65
66      def test_append(self):
67          a_list = [0, 1, 2, 'n']
68          self.assertIsNone(a_list.append())
69
70
71  'append',

the terminal shows TypeError

TypeError: list.append() takes exactly one argument (0 given)

GREEN: make it pass

I add 0 as input

68        self.assertIsNone(a_list.append(0))

the terminal shows green, the append method returns None when called

REFACTOR: make it better

  • I add another assertion to see what append did to the list

    68        self.assertIsNone(a_list.append(0))
    69        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 'n', 0] != [0, 1, 2, 'n']
    

    the append method added a value at the end of the list

  • I change the expectation to match the values in the terminal

    69        self.assertEqual(a_list, [0, 1, 2, 'n', 0])
    

    the test passes

  • I change the value given to append

    68        self.assertIsNone(a_list.append('n+1'))
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 'n', 'n+1'] != [0, 1, 2, 'n', 0]
    
  • I change the expectation to match

    69        self.assertEqual(a_list, [0, 1, 2, 'n', 'n+1'])
    

    the test passes

  • I change the name of the test

    66    def test_append_adds_item_to_end_of_a_list(self):
    67        a_list = [0, 1, 2, 'n']
    68        self.assertIsNone(a_list.append('n+1'))
    69        self.assertEqual(a_list, [0, 1, 2, 'n', 'n+1'])
    
  • I remove append from the TODO list

    69      self.assertEqual(a_list, [0, 1, 2, 'n', 'n+1'])
    70
    71
    72'clear',
    73'copy',
    74'count',
    75'extend',
    76'index',
    77'insert',
    78'pop',
    79'remove',
    80'reverse',
    81'sort'
    

I know how to add things to the end of a list


test_clear_empties_a_list

I add a test for the clear method

69        self.assertEqual(a_list, [0, 1, 2, 'n', 'n+1'])
70
71    def test_clear(self):
72        a_list = [0, 1, 2, 'n']
73        self.assertIsNone(a_list.clear())
74
75
76'clear',

the terminal shows green. The clear method returns None when called

RED: make it fail

I add an assertion to see what clear did to the list

73        self.assertIsNone(a_list.clear())
74        self.assertEqual(a_list, [0, 1, 2, 'n'])

the terminal shows AssertionError

AssertionError: Lists differ: [] != [0, 1, 2, 'n']

the list is now empty

GREEN: make it pass

I change the values to match

74        self.assertEqual(a_list, [])

the test passes

REFACTOR: make it better

  • I change the name of the test to make it “clearer”

    71    def test_clear_empties_a_list(self):
    72        a_list = [0, 1, 2, 'n']
    73        self.assertIsNone(a_list.clear())
    74        self.assertEqual(a_list, [])
    
  • I remove clear from the TODO list

    74        self.assertEqual(a_list, [])
    75
    76
    77'copy',
    78'count',
    79'extend',
    80'index',
    81'insert',
    82'pop',
    83'remove',
    84'reverse',
    85'sort'
    

I know how to empty a list


test_copy_a_list

RED: make it fail

I add another test

74        self.assertEqual(a_list, [])
75
76    def test_copy(self):
77        a_list = [0, 1, 2, 'n']
78        self.assertIsNone(a_list.copy())
79
80
81'copy',

the terminal shows AssertionError

AssertionError: [0, 1, 2, 'n'] is not None

the method returns a copy of the list

GREEN: make it pass

  • I add the list to the assertion

    77        a_list = [0, 1, 2, 'n']
    78        self.assertIsNone(a_list.copy(), [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: [0, 1, 2, 'n'] is not None : [0, 1, 2, 'n']
    

    the values are the same, the problem is assertIsNone only takes 1 input and I gave it 2

  • I change assertIsNone to assertEqual

    77        a_list = [0, 1, 2, 'n']
    78        self.assertEqual(a_list.copy(), [0, 1, 2, 'n'])
    

    the test passes

REFACTOR: make it better

  • I change the name of the test

    76    def test_copy_a_list(self):
    77        a_list = [0, 1, 2, 'n']
    78        self.assertEqual(a_list.copy(), [0, 1, 2, 'n'])
    
  • I remove copy from the TODO list

    78        self.assertIsNone(a_list.copy(), [0, 1, 2, 'n'])
    79
    80
    81'count',
    82'extend',
    83'index',
    84'insert',
    85'pop',
    86'remove',
    87'reverse',
    88'sort'
    

I know how to copy a list


test_count_number_of_times_item_is_in_a_list

RED: make it fail

I add a test for the next method

78        self.assertEqual(a_list.copy(), [0, 1, 2, 'n'])
79
80    def test_count(self):
81        a_list = [0, 1, 2, 'n']
82        self.assertIsNone(a_list.count())
83
84
85'count',

the terminal shows TypeError

TypeError: list.count() takes exactly one argument (0 given)

I add a value to the call

80    def test_count(self):
81        a_list = [0, 1, 2, 'n']
82        self.assertIsNone(a_list.count(0))

the terminal shows AssertionError

AssertionError: 1 is not None

GREEN: make it pass

I add the value

82        self.assertIsNone(a_list.count(0), 1)

the terminal shows AssertionError

AssertionError: 1 is not None : 1

I change assertIsNone to assertEqual

82        self.assertEqual(a_list.count(0), 1)

the test passes

REFACTOR: make it better

  • 0 is in this list 1 time, I add 1 to the list 2 more times then add an assertion for it

    80    def test_count(self):
    81        a_list = [0, 1, 2, 1, 'n', 1]
    82        self.assertEqual(a_list.count(0), 1)
    83        self.assertEqual(a_list.count(1), 1)
    

    the terminal shows AssertionError

    AssertionError: 3 != 1
    
  • I change the value to match

    83        self.assertEqual(a_list.count(1), 3)
    

    the test passes

  • I want to see what happens when I try to count something that is not in the list

    83        self.assertEqual(a_list.count(1), 3)
    84        self.assertEqual(a_list.count('not in list'), 3)
    

    the terminal shows AssertionError

    AssertionError: 0 != 3
    

    The count method returns 0 when the item is not in the list

  • I change the value to match

    84        self.assertEqual(a_list.count('not in list'), 0)
    

    the test passes

  • I change the name of the test

    80    def test_count_number_of_times_item_is_in_a_list(self):
    81        a_list = [0, 1, 2, 1, 'n', 1]
    82        self.assertEqual(a_list.count(0), 1)
    83        self.assertEqual(a_list.count(1), 3)
    84        self.assertEqual(a_list.count('not in list'), 0)
    
  • I remove count from the TODO list

    84        self.assertEqual(a_list.count('not in list'), 0)
    85
    86
    87'extend',
    88'index',
    89'insert',
    90'pop',
    91'remove',
    92'reverse',
    93'sort'
    

I know how to count how many times something is in a list


test_extend_adds_items_from_an_iterable_to_end_of_a_list

RED: make it fail

time for another test

84        self.assertEqual(a_list.count('not in list'), 0)
85
86    def test_extend(self):
87        a_list = [0, 1, 2, 'n']
88        self.assertIsNone(a_list.extend())
89
90
91'extend',

the terminal shows TypeError

TypeError: list.extend() takes exactly one argument (0 given)

GREEN: make it pass

I pass a value to the call

88        self.assertIsNone(a_list.extend(0))

the terminal shows TypeError

TypeError: 'int' object is not iterable

I change the value to an iterable

88        self.assertIsNone(a_list.extend((0, 1)))

the test passes. The extend method returns None when called

REFACTOR: make it better

  • I add another assertion to see what it did to the list

    88        self.assertIsNone(a_list.extend((0, 1)))
    89        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 'n', 0, 1] != [0, 1, 2, 'n']
    
  • I change the expectation to match

    89        self.assertEqual(a_list, [0, 1, 2, 'n', 0, 1])
    

    the test passes

  • I change the values given to the extend method

    86    def test_extend(self):
    87        a_list = [0, 1, 2, 'n']
    88        self.assertIsNone(a_list.extend((2, 1, 0)))
    89        self.assertEqual(a_list, [0, 1, 2, 'n', 0, 1])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 'n', 2, 1, 0] != [0, 1, 2, 'n', 0, 1]
    
  • I change the values to match

    89        self.assertEqual(a_list, [0, 1, 2, 'n', 2, 1, 0])
    

    the test is green again, it looks like extend calls append for each item in the iterable

  • I change the name of the test

    86    def test_extend_adds_items_from_an_iterable_to_end_of_a_list(self):
    87        a_list = [0, 1, 2, 'n']
    88        self.assertIsNone(a_list.extend((2, 1, 0)))
    89        self.assertEqual(a_list, [0, 1, 2, 'n', 2, 1, 0])
    
  • I remove extend from the TODO list

    89        self.assertEqual(a_list, [0, 1, 2, 'n', 2, 1, 0])
    90
    91
    92'index',
    93'insert',
    94'pop',
    95'remove',
    96'reverse',
    97'sort'
    

I know how to add items from an iterable to a list


test_index_returns_first_position_of_item_in_a_list

RED: make it fail

I add a test for the index method

89        self.assertEqual(a_list, [0, 1, 2, 'n', 2, 1, 0])
90
91    def test_index(self):
92        a_list = [0, 1, 2, 'n']
93        self.assertIsNone(a_list.index())
94
95
96'index',

the terminal shows TypeError

TypeError: index expected at least 1 argument, got 0

GREEN: make it pass

I add a value to the call

91    def test_index(self):
92        a_list = [0, 1, 2, 'n']
93        self.assertIsNone(a_list.index(0))

the terminal shows AssertionError

AssertionError: 0 is not None

I add the expectation

93        self.assertIsNone(a_list.index(0), 0)

the terminal shows AssertionError

AssertionError: 0 is not None : 0

I change assertIsNone to assertEqual

91    def test_index(self):
92        a_list = [0, 1, 2, 'n']
93        self.assertEqual(a_list.index(0), 0)

the test passes

REFACTOR: make it better

  • maybe the method returned the same value I gave, I change the list to find out

    91    def test_index(self):
    92        a_list = ['1st', '2nd', '3rd', '...last']
    93        self.assertEqual(a_list.index(0), 0)
    

    the terminal shows ValueError

    ValueError: 0 is not in list
    

    the index method raises ValueError when the item is not in the list

  • I add ValueError to the list of Exceptions seen

    104# Exceptions seen
    105# AssertionError
    106# TypeError
    107# ValueError
    
  • I remove the things around the call and change the value to be clearer

    91    def test_index(self):
    92        a_list = ['1st', '2nd', '3rd', '...last']
    93        a_list.index('not in list')
    

    the terminal shows ValueError

    ValueError: 'not in list' is not in list
    
  • I add assertRaises to handle the Exception

    91    def test_index(self):
    92        a_list = ['1st', '2nd', '3rd', '...last']
    93
    94        with self.assertRaises(ValueError):
    95            a_list.index('not in list')
    

    the test is green again

  • I add a new assertion

    91    def test_index(self):
    92        a_list = ['1st', '2nd', '3rd', '...last']
    93        self.assertEqual(a_list.index('1st'), '1st')
    94
    95        with self.assertRaises(ValueError):
    96            a_list.index('not in list')
    

    the terminal shows AssertionError

    AssertionError: 0 != '1st'
    
  • I change the expectation

    93    self.assertEqual(a_list.index('1st'), 0)
    

    the test passes. The index method does not just return the value I give it

  • I add another assertion

    91    def test_index(self):
    92        a_list = ['1st', '2nd', '3rd', '...last']
    93        self.assertEqual(a_list.index('1st'), 0)
    94        self.assertEqual(a_list.index('3rd'), 0)
    

    the terminal shows AssertionError

    AssertionError: 2 != 0
    
  • I change the value in the test

    94    self.assertEqual(a_list.index('3rd'), 2)
    

    the test passes

  • I add another assertion

    94      self.assertEqual(a_list.index('3rd'), 2)
    95      self.assertEqual(a_list.index('2nd'), 2)
    96
    97      with self.assertRaises(ValueError):
    98          a_list.index('not in list')
    

    the terminal shows AssertionError

    AssertionError: 1 != 2
    
  • I change the value to match

    95        self.assertEqual(a_list.index('2nd'), 1)
    

    the test is green again

  • I add another assertion

    95        self.assertEqual(a_list.index('2nd'), 1)
    96        self.assertEqual(a_list.index('...last'), 1)
    

    the terminal shows AssertionError

    AssertionError: 3 != 1
    
  • I change the value to match the terminal

    96        self.assertEqual(a_list.index('...last'), 3)
    

    the test passes. The index method returns numbers for the position of the item in the list. Python uses zero-based indexing which means the first item has an index of 0 and the last item has an index of the length of the list minus 1

  • I want to know what happens when I have the same item in the list more than once, so I add something in the list again

    91    def test_index(self):
    92        a_list = ['1st', '2nd', '3rd', '...last', '1st']
    

    the terminal still shows green

  • I add an assertion

    96        self.assertEqual(a_list.index('...last'), 3)
    97        self.assertEqual(a_list.index('1st'), 4)
    

    the terminal shows AssertionError

    AssertionError: 0 != 4
    

    when I first called the index method, the terminal showed TypeError

    TypeError: index expected at least 1 argument, got 0
    

    which means I should be able to send more than one argument

  • I add a second argument

    97        self.assertEqual(a_list.index('1st', 0), 4)
    

    the terminal still shows AssertionError

    AssertionError: 0 != 4
    
  • I change the argument

    97self.assertEqual(a_list.index('1st', 1), 4)
    

    the test passes, the second input is the position I want the method to start from

  • I try the same thing with the other assertions

     91    def test_index(self):
     92        a_list = ['1st', '2nd', '3rd', '...last', '1st']
     93        self.assertEqual(a_list.index('1st', 0), 0)
     94        self.assertEqual(a_list.index('3rd', 0), 2)
     95        self.assertEqual(a_list.index('2nd', 0), 1)
     96        self.assertEqual(a_list.index('...last', 0), 3)
     97        self.assertEqual(a_list.index('1st', 1), 4)
     98
     99        with self.assertRaises(ValueError):
    100            a_list.index('not in list')
    

    the test is still green

  • I change the name of the test

    91    def test_index_returns_first_position_of_item_in_a_list(self):
    92        a_list = ['1st', '2nd', '3rd', '...last', '1st']
    
  • I also remove index from the TODO list

     99        with self.assertRaises(ValueError):
    100            a_list.index('not in list')
    101
    102
    103'insert',
    104'pop',
    105'remove',
    106'reverse',
    107'sort'
    

I know how to find the position of something in a list


test_insert_item_at_position_in_a_list

RED: make it fail

I add a test for the next method

 99        with self.assertRaises(ValueError):
100            a_list.index('not in list')
101
102    def test_insert(self):
103        a_list = [0, 1, 2, 'n']
104        self.assertIsNone(a_list.insert())
105
106
107'insert',

the terminal shows TypeError

TypeError: insert expected 2 arguments, got 0

GREEN: make it pass

I add two values to the call

102    def test_insert(self):
103        a_list = [0, 1, 2, 'n']
104        self.assertIsNone(a_list.insert(0, 1))

the test passes. What did the insert method do to the list?

REFACTOR: make it better

  • I add an assertion to find out

    104        self.assertIsNone(a_list.insert(0, 1))
    105        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [1, 0, 1, 2, 'n'] != [0, 1, 2, 'n']
    

    there is an extra value at the beginning of the list

  • I add the new value to the expectation

    105        self.assertEqual(a_list, [1, 0, 1, 2, 'n'])
    

    the test passes.

    The insert method

    • puts the second input given at the index of the list given as the first input

    • it also moves the original items from that index and after in the list, to the right

  • I change the second input in the call

    102    def test_insert(self):
    103        a_list = [0, 1, 2, 'n']
    104        self.assertIsNone(a_list.insert(0, -1))
    105        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [-1, 0, 1, 2, 'n'] != [1, 0, 1, 2, 'n']
    
  • I change the expectation to match

    105        self.assertEqual(a_list, [-1, 0, 1, 2, 'n'])
    

    the test is green again

  • I add another assertion to see what happens when I insert an item in the middle of the list

    105        self.assertEqual(a_list, [-1, 0, 1, 2, 'n'])
    106        self.assertIsNone(a_list.insert(3, 1.5))
    

    the terminal shows green

  • I add an assertion to see what it did to the list

    106        self.assertIsNone(a_list.insert(3, 1.5))
    107        self.assertEqual(a_list, [-1, 0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [-1, 0, 1, 1.5, 2, 'n'] != [-1, 0, 1, 2, 'n']
    
  • I add the value to the expectation

    107        self.assertEqual(a_list, [-1, 0, 1, 1.5, 2, 'n'])
    

    the test passes, it moved everything in the list from the index I gave and after, to the right

  • I change the name of the test

    102    def test_insert_item_at_position_in_a_list(self):
    103        a_list = [0, 1, 2, 'n']
    104        self.assertIsNone(a_list.insert(0, -1))
    105        self.assertEqual(a_list, [-1, 0, 1, 2, 'n'])
    106        self.assertIsNone(a_list.insert(3, 1.5))
    107        self.assertEqual(a_list, [-1, 0, 1, 1.5, 2, 'n'])
    108
    109
    110'insert',
    
  • I remove insert from the TODO list

    107        self.assertEqual(a_list, [-1, 0, 1, 1.5, 2, 'n'])
    108
    109
    110'pop',
    111'remove',
    112'reverse',
    113'sort'
    

I know how to put an item at a position in a list


test_pop_removes_and_returns_last_item_from_a_list

RED: make it fail

I add a new test

107        self.assertEqual(a_list, [-1, 0, 1, 1.5, 2, 'n'])
108
109    def test_pop(self):
110        a_list = [0, 1, 2, 'n']
111        self.assertIsNone(a_list.pop())
112
113
114'pop',

the terminal shows AssertionError

AssertionError: 'n' is not None

The pop method returns the last item in the list

GREEN: make it pass

I add the expectation

111        self.assertIsNone(a_list.pop(), 'n')

the terminal shows AssertionError

AssertionError: 'n' is not None : n

I change assertIsNone to assertEqual

109    def test_pop(self):
110        a_list = [0, 1, 2, 'n']
111        self.assertEqual(a_list.pop(), 'n')

the test passes

REFACTOR: make it better

  • I add another assertion to see what happened to the list

    111        self.assertEqual(a_list.pop(), 'n')
    112        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2] != [0, 1, 2, 'n']
    
  • I change the values in the test to match

    112        self.assertEqual(a_list, [0, 1, 2])
    

    the test passes

  • I add another assertion with the pop method

    112        self.assertEqual(a_list, [0, 1, 2])
    113        self.assertEqual(a_list.pop(), 'n')
    

    the terminal shows AssertionError

    AssertionError: 2 != 'n'
    
  • I change the value in the expectation

    113        self.assertEqual(a_list.pop(), 2)
    

    the test passes

  • I add another assertion to see what changed in the list

    113        self.assertEqual(a_list.pop(), 2)
    114        self.assertEqual(a_list, [0, 1, 2])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1] != [0, 1, 2]
    
  • I change the values in the test to match

    114        self.assertEqual(a_list, [0, 1])
    

    the test passes

  • I change the name of the test

        def test_pop_removes_and_returns_last_item_from_a_list(self):
            a_list = [0, 1, 2, 'n']
            self.assertEqual(a_list.pop(), 'n')
            self.assertEqual(a_list, [0, 1, 2])
            self.assertEqual(a_list.pop(), 2)
            self.assertEqual(a_list, [0, 1])
    
    
    'pop',
    
  • I take out pop from the TODO list

    114        self.assertEqual(a_list, [0, 1])
    115
    116
    117'remove',
    118'reverse',
    119'sort'
    

I know how to get and remove the last item from a list


test_remove_first_time_item_is_in_a_list

RED: make it fail

time for the next method

114        self.assertEqual(a_list, [0, 1])
115
116    def test_remove(self):
117        a_list = [0, 1, 2, 'n']
118        self.assertIsNone(a_list.remove())
119
120
121'remove',

the terminal shows TypeError

TypeError: list.remove() takes exactly one argument (0 given)

GREEN: make it pass

I add a value to the call

118        self.assertIsNone(a_list.remove(0))

the terminal shows green. The remove method returns None

REFACTOR: make it better

  • I add an assertion to see what it did to the list

    118        self.assertIsNone(a_list.remove(0))
    119        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [1, 2, 'n'] != [0, 1, 2, 'n']
    

    the method removes the item given as input from the list

  • I change the expectation

    119        self.assertEqual(a_list, [1, 2, 'n'])
    

    the test passes

  • I change the values in the list to see what happens when an item is in there more than one time

    116    def test_remove(self):
    117        a_list = [0, 1, 0, 2, 0, 'n']
    118        self.assertIsNone(a_list.remove(0))
    119        self.assertEqual(a_list, [1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [1, 0, 2, 0, 'n'] != [1, 2, 'n']
    
  • I change the values to match

    119        self.assertEqual(a_list, [1, 0, 2, 0, 'n'])
    

    the test passes. The remove method only removes the first time something shows up in the list

  • I want to see what happens when I try to remove something that is not in the list

    119        self.assertEqual(a_list, [1, 0, 2, 0, 'n'])
    120        self.assertIsNone(a_list.remove('not in list'))
    

    the terminal shows ValueError

    ValueError: list.remove(x): x not in list
    
  • I remove the things around the call then add assertRaises

    119        self.assertEqual(a_list, [1, 0, 2, 0, 'n'])
    120
    121        with self.assertRaises(ValueError):
    122            a_list.remove('not in list')
    123
    124
    125'remove',
    

    the test passes

  • I change the name of the test

    116    def test_remove_first_time_item_is_in_a_list(self):
    117        a_list = [0, 1, 0, 2, 0, 'n']
    118        self.assertIsNone(a_list.remove(0))
    119        self.assertEqual(a_list, [1, 0, 2, 0, 'n'])
    120
    121        with self.assertRaises(ValueError):
    122            a_list.remove('not in list')
    123
    124
    125'remove'
    
  • I take out remove from the TODO list

    121        with self.assertRaises(ValueError):
    122            a_list.remove('not in list')
    123
    124
    125'reverse',
    126'sort'
    

I know how to remove the first time something is in a list


test_reverse_a_list

RED: make it fail

  • I add the next test

    121        with self.assertRaises(ValueError):
    122            a_list.remove('not in list')
    123
    124    def test_reverse(self):
    125        a_list = [0, 1, 2, 'n']
    126        self.assertIsNone(a_list.reverse())
    127
    128
    129'reverse',
    

    the terminal shows green. The reverse method returns None.

  • I add an assertion to see what it did to the list

    126        self.assertIsNone(a_list.reverse())
    127        self.assertEqual(a_list, [0, 1, 2, 'n'])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['n', 2, 1, 0] != [0, 1, 2, 'n']
    

    it reversed the order of the items in the list

GREEN: make it pass

I change the expectation

127        self.assertEqual(a_list, ['n', 2, 1, 0])

the test passes

REFACTOR: make it better

  • I change the name of the test

    124    def test_reverse_a_list(self):
    125        a_list = [0, 1, 2, 'n']
    126        self.assertIsNone(a_list.reverse())
    127        self.assertEqual(a_list, ['n', 2, 1, 0])
    128
    129
    130'reverse',
    
  • I remove the name from the TODO list

    127        self.assertEqual(a_list, ['n', 2, 1, 0])
    128
    129
    130'sort'
    131
    132
    133# Exceptions seen
    

I know how to reverse a list


test_sort_a_list

RED: make it fail

I add a test for the last method in the TODO list

127        self.assertEqual(a_list, ['n', 2, 1, 0])
128
129    def test_sort(self):
130        a_list = [0, 1, 2, 'n']
131        self.assertIsNone(a_list.sort())
132
133
134'sort'

the terminal shows TypeError

TypeError: '<' not supported between instances of 'str' and 'int'

I have to change 'n' to a number or change the other numbers to strings

GREEN: make it pass

I remove the things around the call then add assertRaises

129    def test_sort(self):
130        a_list = [0, 1, 2, 'n']
131        with self.assertRaises(TypeError):
132            a_list.sort()

the test passes

REFACTOR: make it better

  • I can use the list directly in the call

    129  def test_sort(self):
    130      a_list = [0, 1, 2, 'n']
    131      with self.assertRaises(TypeError):
    132          [0, 1, 2, 'n'].sort()
    

    the test is still green

  • I remove the variable because it is no longer used

    129    def test_sort(self):
    130        with self.assertRaises(TypeError):
    131            [0, 1, 2, 'n'].sort()
    

    still green

  • I add a new list where all the items are numbers and another assertion

    129    def test_sort(self):
    130        with self.assertRaises(TypeError):
    131            [0, 1, 2, 'n'].sort()
    132
    133        a_list = [0, 1, 2, 3]
    134        self.assertIsNone(a_list.sort())
    

    the terminal still shows green, sort returns None when called

  • I add another assertion to see what it did to the list

    133        a_list = [0, 1, 2, 3]
    134        self.assertIsNone(a_list.sort())
    135        self.assertEqual(a_list, [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [0, 1, 2, 3] != []
    

    the list stayed the same

  • I change the expectation to match

    135        self.assertEqual(a_list, [0, 1, 2, 3])
    

    the test passes. The name of the method is sort and I gave it a list that is sorted

  • I change the list to see what happens when it is NOT sorted

    133        a_list = [0, 1, -1, 2, -2, 3, -3]
    134        self.assertIsNone(a_list.sort())
    135        self.assertEqual(a_list, [0, 1, 2, 3])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: [-3, -2, -1, 0, 1, 2, 3] != [0, 1, 2, 3]
    

    the sort method changed the order of the list from smallest to biggest number

  • I change the values to match

    135        self.assertEqual(a_list, [-3, -2, -1, 0, 1, 2, 3])
    

    the test passes

  • I change the name of the test

    129    def test_sort_a_list(self):
    130        with self.assertRaises(TypeError):
    131            [0, 1, 2, 'n'].sort()
    132
    133        a_list = [0, 1, -1, 2, -2, 3, -3]
    134        self.assertIsNone(a_list.sort())
    135        self.assertEqual(a_list, [-3, -2, -1, 0, 1, 2, 3])
    136
    137
    138'sort'
    
  • I remove sort from the TODO list

    135        self.assertEqual(a_list, [-3, -2, -1, 0, 1, 2, 3])
    136
    137
    138# Exceptions seen
    139# AssertionError
    140# TypeError
    141# ValueError
    

I know how to sort a list


test_getting_items_of_a_list

When I want an item that is in a list, I can give its index in square brackets([]) to get it

RED: make it fail

I add a failing test

135        self.assertEqual(a_list, [-3, -2, -1, 0, 1, 2, 3])
136
137    def test_getting_items_of_a_list(self):
138        a_list = ['1st', '2nd', '3rd', '...last']
139        self.assertEqual(a_list[0], '')
140
141
142# Exceptions seen

the terminal shows AssertionError

AssertionError: '1st' != ''

the first item has an index of 0

GREEN: make it pass

I change the value in the test

139        self.assertEqual(a_list[0], '1st')

the test passes

REFACTOR: make it better

  • this is the opposite of the index method which takes in the item and returns its position, in this case I provide the index (position) and it returns the item, which means I can write this

    139        self.assertEqual(a_list[0], '1st')
    140        self.assertEqual(a_list[a_list.index('1st')], '')
    

    the terminal shows AssertionError

    AssertionError: '1st' != ''
    

    I change the value to match

    140        self.assertEqual(a_list[a_list.index('1st')], '1st')
    

    the test passes because

    • a_list.index('1st') returns 0

    • a_list[0] returns 1st

  • I can also use negative numbers. The last item has an index of -1 and the first item has an index of negative the length of the list

    140        self.assertEqual(a_list[a_list.index('1st')], '1st')
    141        self.assertEqual(a_list[-4], '')
    

    the terminal shows AssertionError

    AssertionError: '1st' != ''
    

    I change the value to match

    141        self.assertEqual(a_list[-4], '1st')
    

    the test passes

  • I add another assertion

    141        self.assertEqual(a_list[-4], '1st')
    142        self.assertEqual(a_list[2], '')
    

    the terminal shows AssertionError

    AssertionError: '3rd' != ''
    

    I change the expectation to match

    142        self.assertEqual(a_list[2], '3rd')
    

    the terminal shows green again

  • I add another assertion

    142        self.assertEqual(a_list[2], '3rd')
    143        self.assertEqual(a_list[-2], '')
    

    the terminal shows AssertionError

    AssertionError: '3rd' != ''
    

    I change the expectation

    143        self.assertEqual(a_list[-2], '3rd')
    

    the test passes

  • I add another line

    143        self.assertEqual(a_list[-2], '3rd')
    144        self.assertEqual(a_list[1], '')
    

    the terminal shows AssertionError

    AssertionError: '2nd' != ''
    

    I change the expectation

    144        self.assertEqual(a_list[1], '2nd')
    
  • I add another

    144        self.assertEqual(a_list[1], '2nd')
    145        self.assertEqual(a_list[-3], '')
    

    the terminal shows AssertionError

    AssertionError: '2nd' != ''
    

    I change the value

    145        self.assertEqual(a_list[-3], '2nd')
    

    the test passes

  • I add another assertion

    145        self.assertEqual(a_list[-3], '2nd')
    146        self.assertEqual(a_list[3], '')
    

    the terminal shows AssertionError

    AssertionError: '...last' != ''
    

    I change the value

    146        self.assertEqual(a_list[3], '...last')
    

    the test passes

  • I add another assertion

    146        self.assertEqual(a_list[3], '...last')
    147        self.assertEqual(a_list[-1], '')
    

    the terminal shows AssertionError

    AssertionError: '...last' != ''
    

    I make the values match

    137    def test_getting_items_of_a_list(self):
    138        a_list = ['1st', '2nd', '3rd', '...last']
    139        self.assertEqual(a_list[0], '1st')
    140        self.assertEqual(a_list[a_list.index('1st')], '1st')
    141        self.assertEqual(a_list[-4], '1st')
    142        self.assertEqual(a_list[2], '3rd')
    143        self.assertEqual(a_list[-2], '3rd')
    144        self.assertEqual(a_list[1], '2nd')
    145        self.assertEqual(a_list[-3], '2nd')
    146        self.assertEqual(a_list[3], '...last')
    147        self.assertEqual(a_list[-1], '...last')
    148
    149
    150# Exceptions seen
    

    the test passes

I know how to get things that are in a list


test_setting_items_in_a_list

I can also use the index of an item in a list to change the item

RED: make it fail

I add an assertion

147        self.assertEqual(a_list[-1], '...last')
148
149    def test_setting_items_in_a_list(self):
150        a_list = ['1st', '2nd', '3rd', '...last']
151        a_list[-1] = '4th'
152        self.assertEqual(a_list, ['1st', '2nd', '3rd', '...last'])
153
154
155# Exceptions seen

the terminal shows AssertionError

AssertionError: Lists differ: ['1st', '2nd', '3rd', '4th'] != ['1st', '2nd', '3rd', '...last']

I can use the index of an item to change its value in a list, the way I point a name to a value when I make a variable

GREEN: make it pass

I change the expectation to match the values in the terminal

152        self.assertEqual(a_list, ['1st', '2nd', '3rd', '4th'])

the test passes. I know how to change the value of an item in a list


test_looking_at_parts_of_a_list_aka_slicing

I can use indices to look at parts of a list, this is called slicing

RED: make it fail

I add another test

152          self.assertEqual(a_list, ['1st', '2nd', '3rd', '4th'])
153
154      def test_looking_at_parts_of_a_list(self):
155          a_list = ['a', 'b', 'c', 'd']
156          self.assertEqual(a_list[0:2], [])
157
158
159  # Exceptions seen

the terminal shows AssertionError

AssertionError: Lists differ: ['a', 'b'] != []

GREEN: make it pass

I change the values to match

156        self.assertEqual(a_list[0:2], ['a', 'b'])

the test passes.

I give two values in square brackets([]), separated by a :, the first value is the index of the item I want to start from, and the second value is the index of the item I want to stop at plus 1

REFACTOR: make it better

  • I can skip the first number when the starting index is 0

    155        self.assertEqual(a_list[0:2], ['a', 'b'])
    156        self.assertEqual(a_list[:2], [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['a', 'b'] != []
    

    I change the values to match

    157        self.assertEqual(a_list[:2], ['a', 'b'])
    

    the terminal shows green again. [0:2] and [:2] are the same

  • I add another assertion

    157        self.assertEqual(a_list[:2], ['a', 'b'])
    158        self.assertEqual(a_list[1:4], [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['b', 'c', 'd'] != []
    

    I add the missing values

    158        self.assertEqual(a_list[1:4], ['b', 'c', 'd'])
    

    the test passes

  • I can skip the second number when it is bigger than or the same as the number of things in the list

    158        self.assertEqual(a_list[1:4], ['b', 'c', 'd'])
    159        self.assertEqual(a_list[1:], [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['b', 'c', 'd'] != []
    

    I add the missing values

    159        self.assertEqual(a_list[1:], ['b', 'c', 'd'])
    

    the test passes. [1:4] and [1:] are the same because this list has 4 things

  • I add another line

    159        self.assertEqual(a_list[1:], ['b', 'c', 'd'])
    160        self.assertEqual(a_list[0:3], [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['a', 'b', 'c'] != []
    

    I change the expectation

    160        self.assertEqual(a_list[0:3], ['a', 'b', 'c'])
    

    the test passes

  • I add another

    160        self.assertEqual(a_list[0:3], ['a', 'b', 'c'])
    161        self.assertEqual(a_list[1:3], [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['b', 'c'] != []
    

    I change the expectation

    161        self.assertEqual(a_list[1:3], ['b', 'c'])
    

    the test is green again

  • I can also skip both numbers

    161        self.assertEqual(a_list[1:3], ['b', 'c'])
    162        self.assertEqual(a_list[:], [])
    

    the terminal shows AssertionError

    AssertionError: Lists differ: ['a', 'b', 'c', 'd'] != []
    

    I get the entire list back or a copy

    162        self.assertEqual(a_list[:], a_list.copy())
    

    the test is green again

  • This is also called slicing, I change the name of the test

    154    def test_looking_at_parts_of_a_list_aka_slicing(self):
    155        a_list = ['a', 'b', 'c', 'd']
    156        self.assertEqual(a_list[0:2], ['a', 'b'])
    157        self.assertEqual(a_list[:2], ['a', 'b'])
    158        self.assertEqual(a_list[1:4], ['b', 'c', 'd'])
    159        self.assertEqual(a_list[1:], ['b', 'c', 'd'])
    160        self.assertEqual(a_list[0:3], ['a', 'b', 'c'])
    161        self.assertEqual(a_list[1:3], ['b', 'c'])
    162        self.assertEqual(a_list[:], a_list.copy())
    163
    164
    165# Exceptions seen
    

I know how to look at parts of a list


test_index_error

IndexError is raised when I try to get an item from a list but use a number that points to something that is NOT in it. When I see this Exception I know the underlying data structure is a list

RED: make it fail

I add a failing test

162        self.assertEqual(a_list[:], a_list.copy())
163
164    def test_index_error(self):
165        a_list = ['a', 'b', 'c', 'd']
166        a_list[4]
167
168
169# Exceptions seen

the terminal shows IndexError

IndexError: list index out of range

when I use an index that is greater than or the same as the number of things in the list, I am pointing to something that is NOT in the list

GREEN: make it pass

I add assertRaises

164def test_index_error(self):
165    a_list = ['a', 'b', 'c', 'd']
166
167    with self.assertRaises(IndexError):
168        a_list[4]

the test passes

REFACTOR: make it better

  • I add IndexError to the list of Exceptions seen

    171# Exceptions seen
    172# AssertionError
    173# TypeError
    174# ValueError
    175# IndexError
    
  • I get IndexError when I use a number that is smaller than the number of things in the list as a negative number

    167        with self.assertRaises(IndexError):
    168            a_list[4]
    169        a_list[-5]
    

    the terminal shows IndexError

    IndexError: list index out of range
    
  • I add the assertRaises method

    167        with self.assertRaises(IndexError):
    168            a_list[4]
    169        with self.assertRaises(IndexError):
    170            a_list[-5]
    171
    172
    173# Exceptions seen
    

    the test passes

  • IndexError is also raised when I call the pop method with an empty list

    167        with self.assertRaises(IndexError):
    168            a_list[4]
    169        with self.assertRaises(IndexError):
    170            a_list[-5]
    171        [].pop()
    

    the terminal shows IndexError

    IndexError: pop from empty list
    
  • I add assertRaises

    169        with self.assertRaises(IndexError):
    170            a_list[-5]
    171        with self.assertRaises(IndexError):
    172            [].pop()
    

    the terminal shows green

  • I cannot remove the last item from a list that has no items, this is the same as trying to get an item from a list that has no items

    171        with self.assertRaises(IndexError):
    172            [].pop()
    173        [][-1]
    

    the terminal shows IndexError

    IndexError: list index out of range
    
  • I add assertRaises

    171        with self.assertRaises(IndexError):
    172            [].pop()
    173        with self.assertRaises(IndexError):
    174            [][-1]
    

    the test passes. Any index given to an empty list raises IndexError

I know what causes IndexError


close the project

  • I close test_lists.py in the editor

  • I click in the terminal and exit the tests with ctrl+c on the keyboard, the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory

Note

on Windows without Windows Subsystem for Linux


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?

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 activate the virtual environment

    source .venv/bin/activate
    

    Attention

    on Windows without Windows Subsystem for Linux use .venv/bin/activate.ps1 instead of source .venv/bin/activate

    .venv/scripts/activate.ps1
    

    the terminal shows

    (.venv) .../pumping_python/calculator
    
  • I use pytest-watch to run the tests

    pytest-watch
    

    the terminal shows

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

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: 'Excuse me?! I only work with numbers. Please try again...' != 'BOOM!!!'

green: make it pass

I change the expectation to match

96        self.assertEqual(
97            src.calculator.add(a_list, 0),
98            'Excuse me?! I only work with numbers. Please try again...'
99        )

the test passes

refactor: make it better

  • I add another assertion for the next function

     96        self.assertEqual(
     97            src.calculator.add(a_list, 0),
     98            'Excuse me?! I only work with numbers. Please try again...'
     99        )
    100        self.assertEqual(
    101            src.calculator.divide(a_list, 1),
    102            'BAP!!!'
    103        )
    

    the terminal shows AssertionError

    AssertionError: 'Excuse me?! I only work with numbers. Please try again...' != 'BAP!!!'
    
  • I change the expectation

    101        self.assertEqual(
    102            src.calculator.divide(a_list, 1),
    103            'Excuse me?! I only work with numbers. Please 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 only_takes_numbers function. Never again

  • I add a 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 = 'Excuse me?! I only work with numbers. Please 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        )
    

    the terminal shows AssertionError

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

    I 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] != 'Excuse me?! I only work with numbers. Please try again...'
    
  • I open calculator.py in the editor

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

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        error_message = 'Excuse me?! I only work with numbers. Please try again...'
     4
     5        if isinstance(first_input, str) or isinstance(second_input, str):
     6            return error_message
     7        if isinstance(first_input, list) or isinstance(second_input, list):
     8            return error_message
     9
    10        try:
    11            return function(first_input, second_input)
    12        except TypeError:
    13            return error_message
    14    return wrapper
    

    the test passes. The only_takes_numbers function looks ugly now, there has to be a better way

  • I add an assertion for the subtract function 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        )
    

    the terminal shows AssertionError

    AssertionError: 'Excuse me?! I only work with numbers. Please try again...' != 'BOOM!!!'
    
  • I change the expectation to match

    109        self.assertEqual(
    110            src.calculator.subtract(a_list, 3),
    111            error_message
    112        )
    

    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

     88        self.assertEqual(
     89            src.calculator.subtract('1', '1'),
     90            error_message
     91        )
     92
     93        a_list = [0, 1, 2, 3]
     94        error_message = 'Excuse me?! I only work with numbers. Please 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        )
    104        self.assertEqual(
    105            src.calculator.multiply(a_list, 2),
    106            error_message
    107        )
    108        self.assertEqual(
    109            src.calculator.subtract(a_list, 3),
    110            error_message
    111        )
    112
    113
    114# Exceptions seen
    

    the tests are still green

  • I remove the duplication of the error_message variable

    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        )
    

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


how to test if something is an instance of more than one type

The isinstance function can take a tuple as the second input, which allows me to check if the first input is an instance of any of the objects in the tuple

  • I add a variable to the only_takes_numbers function in calculator.py

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        bad_types = (str, list)
     4        error_message = 'Excuse me?! I only work with numbers. Please try again...'
     5
     6        # if isinstance(first_input, str) or isinstance(second_input, str):
     7        #     return error_message
     8        # if isinstance(first_input, list) or isinstance(second_input, list):
     9        #     return error_message
    10
    11        if isinstance(first_input, bad_types) or isinstance(second_input, bad_types):
    12            return error_message
    13
    14        try:
    15            return function(first_input, second_input)
    16        except TypeError:
    17            return error_message
    18    return wrapper
    

    the tests are still green

  • I remove the comments

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        bad_types = (str, list)
     4        error_message = 'Excuse me?! I only work with numbers. Please try again...'
     5
     6        if isinstance(first_input, bad_types) or isinstance(second_input, bad_types):
     7            return error_message
     8
     9        try:
    10            return function(first_input, second_input)
    11        except TypeError:
    12            return error_message
    13    return wrapper
    

    still green

  • I can use Logical Negation (NOT) to make the if statement allow only the types of numbers (integers and floats) that I want the calculator to handle

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        good_types = (int, float)
     4        bad_types = (str, list)
     5        error_message = 'Excuse me?! I only work with numbers. Please try again...'
     6
     7        # if isinstance(first_input, bad_types) or isinstance(second_input, bad_types):
     8        #     return error_message
     9
    10        if not isinstance(first_input, good_types) or not isinstance(second_input, good_types):
    11            return error_message
    12        else:
    13            try:
    14                return function(first_input, second_input)
    15            except TypeError:
    16                return error_message
    17    return wrapper
    

    the tests are still green

  • I remove the comments and the bad_types variable

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        good_types = (int, float)
     4        error_message = 'Excuse me?! I only work with numbers. Please try again...'
     5
     6        if not isinstance(first_input, good_types) or not isinstance(second_input, good_types):
     7            return error_message
     8        else:
     9            try:
    10                return function(first_input, second_input)
    11            except TypeError:
    12                return error_message
    13    return wrapper
    

    the tests are still passing

  • not” happens twice in the if statement, I write the line in terms of it

    6        # if not isinstance(first_input, good_types) or not isinstance(second_input, good_types):
    7        if (not isinstance(first_input, good_types)) (not and) (not isinstance(second_input, good_types)):
    8            return error_message
    

    the terminal shows SyntaxError

    SyntaxError: invalid syntax
    
  • I add SyntaxError to the list of Exceptions in test_calculator.py

    113# Exceptions seen
    114# AssertionError
    115# NameError
    116# AttributeError
    117# TypeError
    118# ZeroDivisionError
    119# SyntaxError
    
  • I fix the if statement<if statement> in calculator.py

    6        # if not isinstance(first_input, good_types) or not isinstance(second_input, good_types):
    7        # if (not isinstance(first_input, good_types)) (not and) ((not isinstance(second_input, good_types))):
    8        if not (isinstance(first_input, good_types) and isinstance(second_input, good_types)):
    9            return error_message
    

    the test is green again

  • I remove the comments

     1def only_takes_numbers(function):
     2    def wrapper(first_input, second_input):
     3        good_types = (int, float)
     4        error_message = 'Excuse me?! I only work with numbers. Please try again...'
     5
     6        if not (isinstance(first_input, good_types) and isinstance(second_input, good_types)):
     7            return error_message
     8        else:
     9            try:
    10                return function(first_input, second_input)
    11            except TypeError:
    12                return error_message
    13    return wrapper
    

    all the tests are still passing. I wonder if there is a way to write this function with only one return statement for the error message


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          a_list = [self.random_first_number, self.random_second_number]
114
115          self.assertEqual(
116              src.calculator.add(a_list[0], a_list[1]),
117              self.random_first_number-self.random_second_number
118          )
119
120
121  # Exceptions seen

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(a_list[0], a_list[1]),
117            self.random_first_number+self.random_second_number
118        )

the test passes

refactor: make it better

  • I add an assertion for the divide function

    115        self.assertEqual(
    116            src.calculator.add(a_list[0], a_list[1]),
    117            self.random_first_number+self.random_second_number
    118        )
    119        self.assertEqual(
    120            src.calculator.divide(a_list[-2], a_list[-1]),
    121            self.random_first_number*self.random_second_number
    122        )
    

    the terminal shows AssertionError

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

    119      self.assertEqual(
    120          src.calculator.divide(a_list[-2], a_list[-1]),
    121          self.random_first_number/self.random_second_number
    122      )
    

    the test passes

  • I add another assertion

    119        self.assertEqual(
    120            src.calculator.divide(a_list[-2], a_list[-1]),
    121            self.random_first_number/self.random_second_number
    122        )
    123        self.assertEqual(
    124            src.calculator.multiply(a_list[1], a_list[-1]),
    125            self.random_first_number*self.random_second_number
    126        )
    

    the terminal shows AssertionError

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

    123        self.assertEqual(
    124            src.calculator.multiply(a_list[1], a_list[-1]),
    125            self.random_second_number*self.random_second_number
    126        )
    

    the test passes

  • I add an assertion for the subtract function

    123        self.assertEqual(
    124            src.calculator.multiply(a_list[1], a_list[-1]),
    125            self.random_second_number*self.random_second_number
    126        )
    127        self.assertEqual(
    128            src.calculator.subtract(a_list[-2], a_list[0]),
    129            self.random_first_number-self.random_second_number
    130        )
    

    the terminal shows AssertionError

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

    127        self.assertEqual(
    128            src.calculator.subtract(a_list[-2], a_list[0]),
    129            self.random_first_number-self.random_first_number
    130        )
    

    the test passes

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

    127        self.assertEqual(
    128            src.calculator.subtract(a_list[-2], a_list[0]),
    129            self.random_first_number-self.random_first_number
    130        )
    131        self.assertEqual(
    132            src.calculator.add(*a_list),
    133            self.random_first_number-self.random_second_number
    134        )
    

    the terminal shows AssertionError

    AssertionError: GHI.JKLMNOPQRSTUVW != XYZ.ABCDEFGHIJKLMN
    
  • I change the expectation

    131        self.assertEqual(
    132            src.calculator.add(*a_list),
    133            self.random_first_number+self.random_second_number
    134        )
    

    the test passes

  • I add another assertion

    131        self.assertEqual(
    132            src.calculator.add(*a_list),
    133            self.random_first_number+self.random_second_number
    134        )
    135        self.assertEqual(
    136            src.calculator.divide(*a_list),
    137            self.random_first_number*self.random_second_number
    138        )
    

    the terminal shows AssertionError

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

    135        self.assertEqual(
    136            src.calculator.divide(*a_list),
    137            self.random_first_number/self.random_second_number
    138        )
    

    the test passes

  • I add an assertion for the multiply function

    135        self.assertEqual(
    136            src.calculator.divide(*a_list),
    137            self.random_first_number/self.random_second_number
    138        )
    139        self.assertEqual(
    140            src.calculator.multiply(*a_list),
    141            self.random_first_number/self.random_second_number
    142        )
    

    the terminal shows AssertionError

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

    139        self.assertEqual(
    140            src.calculator.multiply(*a_list),
    141            self.random_first_number*self.random_second_number
    142        )
    

    the test passes

  • I add the next assertion

    139        self.assertEqual(
    140            src.calculator.multiply(*a_list),
    141            self.random_first_number*self.random_second_number
    142        )
    143        self.assertEqual(
    144            src.calculator.subtract(*a_list),
    145            self.random_first_number+self.random_second_number
    146        )
    

    the terminal shows AssertionError

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

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

    the test passes


test_calculator_raises_type_error_when_given_more_than_two_inputs

It is important to note that the star expression always gives the items from the list in order, and I cannot use a list that has more than 2 items 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 star expression

143        self.assertEqual(
144            src.calculator.subtract(*a_list),
145            self.random_first_number-self.random_second_number
146        )
147
148    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
149        another_list = [0, 1, 2]
150
151        src.calculator.add(*another_list)
152
153
154# Exceptions seen

the terminal shows TypeError

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

green: make it pass

I add the assertRaises method to handle the Exception

148    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
149        another_list = [0, 1, 2]
150
151        with self.assertRaises(TypeError):
152            src.calculator.add(*another_list)

the test passes

refactor: make it better

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

    151        with self.assertRaises(TypeError):
    152            src.calculator.add(*another_list)
    153        src.calculator.divide(*another_list)
    

    the terminal shows TypeError

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

    151        with self.assertRaises(TypeError):
    152            src.calculator.add(*another_list)
    153        with self.assertRaises(TypeError):
    154            src.calculator.divide(*another_list)
    155
    156the test passes
    
  • I add a line for multiplication

    153        with self.assertRaises(TypeError):
    154            src.calculator.divide(*another_list)
    155        src.calculator.multiply(*another_list)
    

    the terminal shows TypeError

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

    153        with self.assertRaises(TypeError):
    154            src.calculator.divide(*another_list)
    155        with self.assertRaises(TypeError):
    156            src.calculator.multiply(*another_list)
    

    the test passes

  • I add the last line

    155        with self.assertRaises(TypeError):
    156            src.calculator.multiply(*another_list)
    157        src.calculator.subtract(*another_list)
    

    the terminal shows TypeError

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

    148    def test_calculator_raises_type_error_when_given_more_than_two_inputs(self):
    149        another_list = [0, 1, 2]
    150
    151        with self.assertRaises(TypeError):
    152            src.calculator.add(*another_list)
    153        with self.assertRaises(TypeError):
    154            src.calculator.divide(*another_list)
    155        with self.assertRaises(TypeError):
    156            src.calculator.multiply(*another_list)
    157        with self.assertRaises(TypeError):
    158            src.calculator.subtract(*another_list)
    

    the test passes


close the project

  • I close test_calculator.py and 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 ran tests to show that I can make a list with the constructor list() or square brackets ([]), then I ran the following tests for the methods of lists from append to sort

I also added tests for


How many questions can you answer after going through this chapter?


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


please leave a review