separate and equal telephone

The text function in the telephone project was written in test_telephone.py. I want to move it to telephone.py in the src folder so that I can keep the tests and solution separate.


preview

I have these tests by the end of the chapter

telephone/tests/test_telephone.py
  1import src.telephone
  2
  3
  4def test_passing_none():
  5    reality = src.telephone.text(None)
  6    my_expectation = 'I got: None'
  7    assert reality == my_expectation
  8
  9
 10def test_passing_booleans():
 11    reality = src.telephone.text(False)
 12    my_expectation = 'I got: False'
 13    assert reality == my_expectation
 14
 15    reality = src.telephone.text(True)
 16    my_expectation = 'I got: True'
 17    assert reality == my_expectation
 18
 19
 20def test_passing_an_integer():
 21    an_integer = 1234
 22
 23    reality = src.telephone.text(an_integer)
 24    my_expectation = f'I got: {an_integer}'
 25    assert reality == my_expectation
 26
 27
 28def test_passing_a_float():
 29    a_float = 5.678
 30
 31    reality = src.telephone.text(a_float)
 32    my_expectation = f'I got: {a_float}'
 33    assert reality == my_expectation
 34
 35
 36def test_passing_a_string():
 37    a_string = 'hello'
 38
 39    reality = src.telephone.text(a_string)
 40    my_expectation = f'I got: {a_string}'
 41    assert reality == my_expectation
 42
 43
 44def test_passing_a_tuple():
 45    a_tuple = (0, 1, 2, 'n')
 46
 47    reality = src.telephone.text(a_tuple)
 48    my_expectation = f'I got: {a_tuple}'
 49    assert reality == my_expectation
 50
 51
 52def test_passing_a_list():
 53    a_list = [0, 1, 2, 'n']
 54
 55    reality = src.telephone.text(a_list)
 56    my_expectation = f'I got: {a_list}'
 57    assert reality == my_expectation
 58
 59
 60def test_passing_a_set():
 61    a_set = {0, 1, 2, 'n'}
 62
 63    reality = src.telephone.text(a_set)
 64    my_expectation = f'I got: {a_set}'
 65    assert reality == my_expectation
 66
 67
 68def test_passing_a_dictionary():
 69    a_dictionary = {
 70        'key0': 'value0',
 71        'keyN': [0, 1, 2, 'n'],
 72    }
 73
 74    reality = src.telephone.text(a_dictionary)
 75    my_expectation = f'I got: {a_dictionary}'
 76    assert reality == my_expectation
 77
 78
 79def test_passing_a_class():
 80    reality = src.telephone.text(object)
 81    my_expectation = "I got: <class 'object'>"
 82    assert reality == my_expectation
 83
 84    reality = src.telephone.text(bool)
 85    my_expectation = "I got: <class 'bool'>"
 86    assert reality == my_expectation
 87
 88    reality = src.telephone.text(int)
 89    my_expectation = "I got: <class 'int'>"
 90    assert reality == my_expectation
 91
 92    reality = src.telephone.text(float)
 93    my_expectation = "I got: <class 'float'>"
 94    assert reality == my_expectation
 95
 96    reality = src.telephone.text(str)
 97    my_expectation = "I got: <class 'str'>"
 98    assert reality == my_expectation
 99
100    reality = src.telephone.text(tuple)
101    my_expectation = "I got: <class 'tuple'>"
102    assert reality == my_expectation
103
104    reality = src.telephone.text(list)
105    my_expectation = "I got: <class 'list'>"
106    assert reality == my_expectation
107
108    reality = src.telephone.text(set)
109    my_expectation = "I got: <class 'set'>"
110    assert reality == my_expectation
111
112    reality = src.telephone.text(dict)
113    my_expectation = "I got: <class 'dict'>"
114    assert reality == my_expectation
115
116
117# Exceptions seen
118# AssertionError
119# NameError
120# TypeError
121# ModuleNotFoundError
122# AttributeError

open the project

  • I open a terminal

  • I change directory to the project

    cd telephone
    

    the terminal shows I am in the telephone folder

    .../pumping_python/telephone
    
  • I open test_telephone.py

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal shows

    test_telephone.py ..........                        [100%]
    
    =================== 10 passed in A.BCs ===================
    

move test_passing_none

RED: make it fail


I change the call in the assertion of test_passing_none to use the result of a call to the text function of the telephone module in the src folder instead of a call to the text function in test_telephone.py

 1def text(the_input):
 2    return f'I got: {the_input}'
 3
 4
 5def test_passing_none():
 6    reality = src.telephone.text(None)
 7    my_expectation = 'I got: None'
 8    # assert text(None) == 'I got: None'
 9    assert reality == my_expectation
10
11
12def test_passing_booleans():

the terminal is my friend, and shows NameError

NameError: name 'src' is not defined

because there is nothing with that name in test_telephone.py.


GREEN: make it pass


  • I add an import statement at the top of test_telephone.py

    1import src
    2
    3
    4def text(the_input):
    5    return f'I got: {the_input}'
    

    the terminal is my friend, and shows ModuleNotFoundError

    E   ModuleNotFoundError: No module named 'src'
    

    because there is nothing named src in the project.

  • I add ModuleNotFoundError to the list of Exceptions seen

    72# Exceptions seen
    73# AssertionError
    74# NameError
    75# TypeError
    76# ModuleNotFoundError
    
  • I open another terminal and make sure I am in the telephone folder

  • I use mkdir to make a folder named src

    mkdir src
    

    the terminal goes back to the command line.

  • I go back to the terminal where the tests are running.

  • I use ctrl/command+s (Windows & Linux/MacOS) in test_telephone.py to run the test again, and the terminal shows AttributeError

    AttributeError: module 'src'
                    has no attribute 'telephone'
    

    because there is nothing named telephone in the src folder.

  • I add AttributeError to the list of Exceptions seen

    72# Exceptions seen
    73# AssertionError
    74# NameError
    75# TypeError
    76# ModuleNotFoundError
    77# AttributeError
    
  • I change the import statement to make it import telephone.py from the src folder

    1# import src
    2import src.telephone
    3
    4
    5def text(the_input):
    

    the terminal is my friend, and shows ModuleNotFoundError

    E   ModuleNotFoundError: No module named 'src.telephone'
    

    because Python cannot find telephone.py in the src folder since I have not made it yet.

  • I go to the other terminal

  • I use touch to make telephone.py in the src folder

    touch src/telephone.py
    
    New-Item src/telephone.py
    

    the terminal goes back to the command line.

  • I add the new file to git for tracking

    git add src/telephone.py
    

    the terminal goes back to the command line.

  • I go back to the terminal where the tests are running and it shows AttributeError

    AttributeError: module 'src.telephone' has no attribute 'text'
    

    because telephone.py in the src folder does not have anything named text inside it.

  • I open telephone/__init__.py from the src folder

  • I add a copy of the text function to telephone.py

    1def text(the_input):
    2    return f'I got: {the_input}'
    

    the test passes because

    • Python brings in an object for the telephone.py file from the src folder so I can use it in test_telephone.py as src.telephone when import src.telephone runs.

    • Python calls the text function from the object it imported for the telephone.py file from the src folder (src.telephone) when src.telephone.text(None) runs.

    I think of src.telephone.text like an address

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the_input}'
    

REFACTOR: make it better


  • I remove the commented lines from test_telephone.py

     1import src.telephone
     2
     3
     4def text(the_input):
     5    return f'I got: {the_input}'
     6
     7
     8def test_passing_none():
     9    reality = src.telephone.text(None)
    10    my_expectation = 'I got: None'
    11    assert reality == my_expectation
    12
    13
    14def test_passing_booleans():
    
  • I change the call in the first assertion of test_passing_booleans to src.telephone.text

    14def test_passing_booleans():
    15    reality = src.telephone.text(False)
    16    my_expectation = 'I got: False'
    17    # assert text(False) == 'I got: False'
    18    assert reality == my_expectation
    19    assert text(True) == 'I got: True'
    20
    21
    22def test_passing_an_integer():
    

    the test is still green because when src.telephone.text is called, Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the_input}'
    

    using the string representation of the object in the curly braces { }

    text(None)
        text(the_input)
            the_input = None
            return f'I got: {the_input}'
            return  'I got:  None      '
    
  • I make the same change for the next assertion

    14def test_passing_booleans():
    15    # assert text(False) == 'I got: False'
    16    reality = src.telephone.text(False)
    17    my_expectation = 'I got: False'
    18    assert reality == my_expectation
    19
    20    reality = src.telephone.text(True)
    21    my_expectation = 'I got: True'
    22    # assert text(True) == 'I got: True'
    23    assert reality == my_expectation
    24
    25
    26def test_passing_an_integer():
    

    still green.

  • I remove the commented lines from test_passing_booleans

    14def test_passing_booleans():
    15    reality = src.telephone.text(False)
    16    my_expectation = 'I got: False'
    17    assert reality == my_expectation
    18
    19    reality = src.telephone.text(True)
    20    my_expectation = 'I got: True'
    21    assert reality == my_expectation
    22
    23
    24def test_passing_an_integer():
    
  • I change the call in the assertion of test_passing_an_integer to src.telephone.text

    24def test_passing_an_integer():
    25    an_integer = 1234
    26
    27    reality = src.telephone.text(an_integer)
    28    my_expectation = f'I got: {an_integer}'
    29    # assert text(an_integer) == f'I got: {an_integer}'
    30    assert reality == my_expectation
    31
    32
    33def test_passing_a_float():
    

    green.

  • I remove the commented line from test_passing_an_integer

    24def test_passing_an_integer():
    25    an_integer = 1234
    26
    27    reality = src.telephone.text(an_integer)
    28    my_expectation = f'I got: {an_integer}'
    29    assert reality == my_expectation
    30
    31
    32def test_passing_a_float():
    
  • I change the call in the assertion of test_passing_a_float

    32def test_passing_a_float():
    33    a_float = 5.678
    34
    35    reality = src.telephone.text(a_float)
    36    my_expectation = f'I got: {a_float}'
    37    # assert text(a_float) == f'I got: {a_float}'
    38    assert reality == my_expectation
    39
    40
    41def test_passing_a_string():
    

    still green because when src.telephone.text is called, Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the_input}'
    

    using the string representation of the object in the curly braces { }

    a_float = 5.678
    
    text(a_float)
        text(the_input)
            the_input = 5.678
            return f'I got: {the_input}'
            return  'I got:  5.678     '
    
  • I remove the commented line

    32def test_passing_a_float():
    33    a_float = 5.678
    34
    35    reality = src.telephone.text(a_float)
    36    my_expectation = f'I got: {a_float}'
    37    assert reality == my_expectation
    38
    39
    40def test_passing_a_string():
    

    the test is still green.

  • I change the call in the assertion of test_passing_a_string

    40def test_passing_a_string():
    41    a_string = 'hello'
    42
    43    reality = src.telephone.text(a_string)
    44    my_expectation = f'I got: {a_string}'
    45    # assert text(a_string) == f'I got: {a_string}'
    46    assert reality == my_expectation
    47
    48
    49def test_passing_a_tuple():
    

    still green.

  • I remove the commented line

    40def test_passing_a_string():
    41    a_string = 'hello'
    42
    43    reality = src.telephone.text(a_string)
    44    my_expectation = f'I got: {a_string}'
    45    assert reality == my_expectation
    46
    47
    48def test_passing_a_tuple():
    
  • I change the call in the assertion of test_passing_a_tuple

    48def test_passing_a_tuple():
    49    a_tuple = (0, 1, 2, 'n')
    50
    51    reality = src.telephone.text(a_tuple)
    52    my_expectation = f'I got: {a_tuple}'
    53    # assert text(a_tuple) == f'I got: {a_tuple}'
    54    assert reality == my_expectation
    55
    56
    57def test_passing_a_list():
    

    green.

  • I remove the commented line

    48def test_passing_a_tuple():
    49    a_tuple = (0, 1, 2, 'n')
    50
    51    reality = src.telephone.text(a_tuple)
    52    my_expectation = f'I got: {a_tuple}'
    53    assert reality == my_expectation
    54
    55
    56def test_passing_a_list():
    
  • I change the call in the assertion of test_passing_a_list

    56def test_passing_a_list():
    57    a_list = [0, 1, 2, 'n']
    58
    59    reality = src.telephone.text(a_list)
    60    my_expectation = f'I got: {a_list}'
    61    # assert text(a_list) == f'I got: {a_list}'
    62    assert reality == my_expectation
    63
    64
    65def test_passing_a_set():
    

    still green.

  • I remove the commented line

    56def test_passing_a_list():
    57    a_list = [0, 1, 2, 'n']
    58
    59    reality = src.telephone.text(a_list)
    60    my_expectation = f'I got: {a_list}'
    61    assert reality == my_expectation
    62
    63
    64def test_passing_a_set():
    
  • I change the call in the assertion of test_passing_a_set

    64def test_passing_a_set():
    65    a_set = {0, 1, 2, 'n'}
    66
    67    reality = src.telephone.text(a_set)
    68    my_expectation = f'I got: {a_set}'
    69    # assert text(a_set) == f'I got: {a_set}'
    70    assert reality == my_expectation
    71
    72
    73def test_passing_a_dictionary():
    

    the test is still green.

  • I remove the commented line

    64def test_passing_a_set():
    65    a_set = {0, 1, 2, 'n'}
    66
    67    reality = src.telephone.text(a_set)
    68    my_expectation = f'I got: {a_set}'
    69    assert reality == my_expectation
    70
    71
    72def test_passing_a_dictionary():
    
  • I change the value the reality variable of test_passing_a_dictionary points to from the result of a call to the text function of the test_telephone module, to the result of a call to the text function of the telephone module in the src folder

    72def test_passing_a_dictionary():
    73    a_dictionary = {
    74        'key0': 'value0',
    75        'keyN': [0, 1, 2, 'n'],
    76    }
    77    # reality = text(a_dictionary)
    78    reality = src.telephone.text(a_dictionary)
    79    my_expectation = f'I got: {a_dictionary}'
    80    assert reality == my_expectation
    81
    82
    83def test_passing_a_class():
    

    still green because when src.telephone.text is called, Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the_input}'
    

    using the string representation of the object in the curly braces { }

    a_dictionary = {
        'key0': 'value0',
        'keyN': [0, 1, 2, 'n'],
    }
    
    text(a_dictionary)
        text(the_input)
            the_input = {
                'key0': 'value0',
                'keyN': [0, 1, 2, 'n'],
            }
            return f'I got: {the_input}'
            return ("I got: {'key0': 'value0',"
                             'keyN': [0, 1, 2, 'n']}")
    
  • I remove the commented line

    72def test_passing_a_dictionary():
    73    a_dictionary = {
    74        'key0': 'value0',
    75        'keyN': [0, 1, 2, 'n'],
    76    }
    77
    78    reality = src.telephone.text(a_dictionary)
    79    my_expectation = f'I got: {a_dictionary}'
    80    assert reality == my_expectation
    81
    82
    83def test_passing_a_class():
    
  • I change the calls in the assertions of test_passing_a_class

     83def test_passing_a_class():
     84    reality = src.telephone.text(object)
     85    my_expectation = "I got: <class 'object'>"
     86    # assert text(object) == "I got: <class 'object'>"
     87    assert reality == my_expectation
     88
     89    reality = src.telephone.text(bool)
     90    my_expectation = "I got: <class 'bool'>"
     91    # assert text(bool) == "I got: <class 'bool'>"
     92    assert reality == my_expectation
     93
     94    reality = src.telephone.text(int)
     95    my_expectation = "I got: <class 'int'>"
     96    # assert text(int) == "I got: <class 'int'>"
     97    assert reality == my_expectation
     98
     99    reality = src.telephone.text(float)
    100    my_expectation = "I got: <class 'float'>"
    101    # assert text(float) == "I got: <class 'float'>"
    102    assert reality == my_expectation
    103
    104    reality = src.telephone.text(str)
    105    my_expectation = "I got: <class 'str'>"
    106    # assert text(str) == "I got: <class 'str'>"
    107    assert reality == my_expectation
    108
    109    reality = src.telephone.text(tuple)
    110    my_expectation = "I got: <class 'tuple'>"
    111    # assert text(tuple) == "I got: <class 'tuple'>"
    112    assert reality == my_expectation
    113
    114    reality = src.telephone.text(list)
    115    my_expectation = "I got: <class 'list'>"
    116    # assert text(list) == "I got: <class 'list'>"
    117    assert reality == my_expectation
    118
    119    reality = src.telephone.text(set)
    120    my_expectation = "I got: <class 'set'>"
    121    # assert text(set) == "I got: <class 'set'>"
    122    assert reality == my_expectation
    123
    124    reality = src.telephone.text(dict)
    125    my_expectation = "I got: <class 'dict'>"
    126    # assert text(dict) == "I got: <class 'dict'>"
    127    assert reality == my_expectation
    128
    129
    130# Exceptions seen
    

    the test is still green.

  • I remove the commented lines from test_passing_a_class

     83def test_passing_a_class():
     84    reality = src.telephone.text(object)
     85    my_expectation = "I got: <class 'object'>"
     86    assert reality == my_expectation
     87
     88    reality = src.telephone.text(bool)
     89    my_expectation = "I got: <class 'bool'>"
     90    assert reality == my_expectation
     91
     92    reality = src.telephone.text(int)
     93    my_expectation = "I got: <class 'int'>"
     94    assert reality == my_expectation
     95
     96    reality = src.telephone.text(float)
     97    my_expectation = "I got: <class 'float'>"
     98    assert reality == my_expectation
     99
    100    reality = src.telephone.text(str)
    101    my_expectation = "I got: <class 'str'>"
    102    assert reality == my_expectation
    103
    104    reality = src.telephone.text(tuple)
    105    my_expectation = "I got: <class 'tuple'>"
    106    assert reality == my_expectation
    107
    108    reality = src.telephone.text(list)
    109    my_expectation = "I got: <class 'list'>"
    110    assert reality == my_expectation
    111
    112    reality = src.telephone.text(set)
    113    my_expectation = "I got: <class 'set'>"
    114    assert reality == my_expectation
    115
    116    reality = src.telephone.text(dict)
    117    my_expectation = "I got: <class 'dict'>"
    118    assert reality == my_expectation
    119
    120
    121# Exceptions seen
    122# AssertionError
    123# NameError
    124# TypeError
    125# ModuleNotFoundError
    126# AttributeError
    
  • I remove the text function from test_telephone.py

     1import src.telephone
     2
     3
     4def test_passing_none():
     5    reality = src.telephone.text(None)
     6    my_expectation = 'I got: None'
     7    assert reality == my_expectation
     8
     9
    10def test_passing_booleans():
    

    all the tests are still green because all the calls that were to the text function of test_telephone.py in the tests folder, now go to the text function of telephone.py in the src folder. When src.telephone.text is called Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the_input}'
    
  • I add a git commit message in the other terminal

    git commit -am \
    'separate solution from tests'
    

    the terminal shows a summary of the changes then goes back to the command line.


test_telephone

Since the solution is separate from the tests, I can write the program that makes the tests pass without looking at test_telephone.py.


RED: make it fail


  • I close test_telephone.py

  • I delete the text in telephone.py and the terminal shows 10 failures. I start with the last AttributeError

    FAILED ...::test_passing_none -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_booleans -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_an_integer -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_float -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_string -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_tuple -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_list -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_set -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_dictionary -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_class -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    =================== 10 failed in A.BCs ===================
    

    Can you make the tests pass without looking at how I solve it below? You can come back to compare solutions when you are done or if you get stuck.


GREEN: make it pass


  • I add the name to telephone.py

    1text
    

    the terminal is my friend, and shows NameError

    NameError: name 'text' is not defined
    
  • I point it to None (the simplest object) to define it

    1# text
    2text = None
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

    because text points to None and I cannot call None like a function.

  • I make text a function to make it callable

    1# text
    2# text = None
    3def text():
    4    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: text() takes 0 positional arguments
               but 1 was given
    

    because this function definition does not allow any inputs, the parentheses are empty.

  • I make the function take input

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    return None
    

    the terminal is my friend, and shows AssertionError

    E       assert None == "I got: <class 'object'>"
    
  • I copy the string from the terminal and paste it in the return statement to match the expectation of the test

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    # return None
    6    return "I got: <class 'object'>"
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'object'>"
                == "I got: <class 'bool'>"
    
  • I change the return statement to see the difference between the input and the expected output (remember the identity function?)

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    # return None
    6    # return "I got: <class 'object'>"
    7    return value
    

    the test summary info shows that every test has AssertionError

    FAILED ...::test_passing_none -
        AssertionError: assert None == 'I got: None'
    FAILED ...::test_passing_booleans -
        AssertionError: assert False == 'I got: False'
    FAILED ...::test_passing_an_integer -
        AssertionError: assert 1234 == 'I got: 1234'
    FAILED ...::test_passing_a_float -
        AssertionError: assert 5.678 == 'I got: 5.678'
    FAILED ...::test_passing_a_string -
        AssertionError: assert 'hello' == 'I got: hello'
    FAILED ...::test_passing_a_tuple -
        assert (0, 1, 2, 'n') == "I got: (0, 1, 2, 'n')"
    FAILED ...::test_passing_a_list -
        assert [0, 1, 2, 'n'] == "I got: [0, 1, 2, 'n']"
    FAILED ...::test_passing_a_set -
        assert {0, 1, 2, 'n'} == "I got: {0, 1, 2, 'n'}"
    FAILED ...::test_passing_a_dictionary -
        assert {'key0': 'value0', 'keyN': [0, 1, 2, 'n']}
    == "I got: {'key0': 'value0',...
    FAILED ...::test_passing_a_class -
        assert <class 'object'> == "I got: <class 'object'>"
    

    they all expect the input (value) as part of the message

  • I add a return statement with an f-string

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    # return None
    6    # return "I got: <class 'object'>"
    7    # return value
    8    return f'I got: {value}'
    

    and all the tests are passing! I am a programmer!!

  • I remove the commented lines

    1def text(value):
    2    return f'I got: {value}'
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'test telephone'
    

    the terminal shows a summary of the changes then goes back to the command line.


close the project

  • I close telephone.py

  • I click in the terminal where the tests are running

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

  • I change directory to the parent of telephone

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory.


review

I can write solutions in a different module from the tests.


code from the chapter

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


what is next?

would you like to separate the tests from the solution in the TypeError project?


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.