separate and equal telephone
The text function in the telephone project was written in tests/test_telephone.py. I want to move it to __init__.py in the telephone folder 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
1import src.telephone
2
3
4text = src.telephone.text
5
6
7def assert_equal(a, b):
8 assert a == b
9
10
11def test_passing_none():
12 assert_equal(text(None), 'I got: None')
15def test_passing_booleans():
16 assert_equal(text(False), 'I got: False')
17 assert_equal(text(True), 'I got: True')
18
19
20def test_passing_an_integer():
21 an_integer = 1234
22 assert_equal(text(an_integer), f'I got: {an_integer}')
23
24
25def test_passing_a_float():
26 a_float = 5.678
27 assert_equal(text(a_float), f'I got: {a_float}')
30def test_passing_a_string():
31 a_string = 'hello'
32 assert_equal(text('hello'), f'I got: {a_string}')
33
34
35def test_passing_a_tuple():
36 a_tuple = (0, 1, 2, 'n')
37 assert_equal(text(a_tuple), f"I got: {a_tuple}")
38
39
40def test_passing_a_list():
41 a_list = [0, 1, 2, 'n']
42 assert_equal(text(a_list), f'I got: {a_list}')
45def test_passing_a_set():
46 a_set = {0, 1, 2, 'n'}
47 assert_equal(text(a_set), f'I got: {a_set}')
48
49
50def test_passing_a_dictionary():
51 a_dictionary = {
52 'key0': 'value0',
53 'keyN': [0, 1, 2, 'n'],
54 }
55 reality = text(a_dictionary)
56 my_expectation = f'I got: {a_dictionary}'
57 assert_equal(reality, my_expectation)
60def test_passing_a_class():
61 assert_equal(
62 text(object), "I got: <class 'object'>"
63 )
64 assert_equal(text(bool), "I got: <class 'bool'>")
65 assert_equal(text(int), "I got: <class 'int'>")
66 assert_equal(text(float), "I got: <class 'float'>")
67 assert_equal(text(str), "I got: <class 'str'>")
68 assert_equal(text(tuple), "I got: <class 'tuple'>")
69 assert_equal(text(list), "I got: <class 'list'>")
70 assert_equal(text(set), "I got: <class 'set'>")
71 assert_equal(text(dict), "I got: <class 'dict'>")
72
73
74# Exceptions seen
75# AssertionError
76# NameError
77# TypeError
78# AttributeError
open the project
I open a terminal
I change directory to the project
cd telephonethe terminal shows I am in the
telephonefolder.../pumping_python/telephoneI open
test_telephone.pyfrom thetestsfolderI use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal shows
test_telephone.py .......... [100%] =================== 10 passed in A.BCs ===================
move text
RED: make it fail
I comment out the text function in tests/test_telephone.py
1def assert_equal(a, b):
2 assert a == b
3
4
5# def text(the_input):
6# return f'I got: {the_input}'
7
8
9def test_passing_none():
the terminal is my friend, and shows NameError
NameError: name 'text' is not defined
GREEN: make it pass
I use a variable to reroute the calls to the
textfunction to thetextfunction of thetelephonemodule in thesrcfolder1text = src.telephone.text 2 3 4def assert_equal(a, b):the terminal is my friend, and shows NameError
NameError: name 'src' is not definedbecause there is nothing with that name in
tests/test_telephone.py.I add an import statement at the top of
tests/test_telephone.py1import src.telephone 2 3 4text = src.telephone.textthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.telephone' has no attribute 'text'because the
telephonemodule in thesrcfolder does not have anything namedtextinside it.I add AttributeError to the list of Exceptions seen
78# Exceptions seen 79# AssertionError 80# NameError 81# TypeError 82# AttributeErrorI open
__init__.pyfrom thetelephonefolder in thesrcfolderI delete all the text in the file then add a copy of the
textfunction tosrc/telephone/__init__.py1def text(the_input): 2 return f'I got: {the_input}'all the tests are green again because
Python brings in an object for the
__init__.pyfile from thetelephonefolder in thesrcfolder so I can use it intests/test_telephone.pyassrc.telephonewhenimport src.telephoneruns.Python calls the
textfunction from the object it imported for thesrc/telephone/__init__.pyfile (src.telephone) whensrc.telephone.textis called.
I think of
src.telephone.textlike an addresssrc.telephone.text src/ └── telephone/ └── __init__.py └── def text(the_input): └── return f'I got: {the_input}'
REFACTOR: make it better
I remove the commented lines from
tests/test_telephone.py1import src.telephone 2 3 4text = src.telephone.text 5 6 7def assert_equal(a, b): 8 assert a == b 9 10 11def test_passing_none():I add a git commit message in the other terminal
git commit -am \ 'move text function to src'the terminal shows a summary of the changes then goes back to the command line.
All the tests are passing because Python follows this path when the text function is called
-
text = src.telephone.text text(None) -> 'I got: None' └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = None └── return f'I got: {the_input}' return 'I got: None ' -
text = src.telephone.text text(False) -> 'I got: False' └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = False └── return f'I got: {the_input}' return 'I got: False ' -
text = src.telephone.text an_integer = 1234 text(an_integer) -> 'I got: 1234' └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = 1234 └── return f'I got: {the_input}' return 'I got: 1234 ' -
text = src.telephone.text a_float = 5.678 text(a_float) -> 'I got: 5.678' └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = 5.678 └── return f'I got: {the_input}' return 'I got: 5.678 ' -
text = src.telephone.text a_string = 'hello' text(a_string) -> 'I got: hello' src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = 'hello' └── return f'I got: {the_input}' return 'I got: hello ' -
text = src.telephone.text a_tuple = (0, 1, 2, 'n') text(a_tuple) -> "I got: (0, 1, 2, 'n')" └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = (0, 1, 2, 'n') └── return f'I got: { the_input }' return "I got: (0, 1, 2, 'n')" -
text = src.telephone.text a_list = [0, 1, 2, 'n'] text(a_list) -> "I got: [0, 1, 2, 'n']" └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = [0, 1, 2, 'n'] └── return f'I got: { the_input }' return "I got: [0, 1, 2, 'n']" -
text = src.telephone.text a_set = {0, 1, 2, 'n'} text(a_set) -> 'I got: {0, 1, 2, 'n'}' └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = {0, 1, 2, 'n'} └── return f'I got: { the_input }' return 'I got: {0, 1, 2, 'n'}' -
text = src.telephone.text a_dictionary = { 'key0': 'value0', 'keyN': [0, 1, 2, 'n'] } text(a_dictionary) -> ("I got: {'key0': 'value0'," │ 'keyN': [0, 1, 2, 'n']}") └── src/ └── telephone/ └── __init__.py └── def 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']}") -
text = src.telephone.text text(object) -> "I got: <class 'object'>" └── src/ └── telephone/ └── __init__.py └── def text(the_input): ├── the_input = object └── return f'I got: { the_input }' return "I got: <class 'object'>"
test_telephone
Since the solution is separate from the tests, I can write the program that makes the tests pass without looking at tests/test_telephone.py.
RED: make it fail
I close
tests/test_telephone.pyI delete the text in
src/telephone/__init__.pyand the terminal AttributeErrorAttributeError: module 'src.telephone' has no attribute 'text'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
src/telephone/__init__.py1textthe terminal is my friend, and shows NameError
NameError: name 'text' is not definedI point it to None (the simplest object) to define it
1# text 2text = Nonethe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause
textpoints to None and I cannot call None like a function.I make
texta function to make it callable1# text 2# text = None 3def text(): 4 return Nonethe terminal is my friend, and shows TypeError
TypeError: text() takes 0 positional arguments but 1 was givenbecause 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 Nonethe 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 valuethe 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', 'keyN': [0, 1, 2, 'n']}" FAILED ...::test_passing_a_class - assert <class 'object'> == "I got: <class 'object'>"they all expect the input (
value) as part of the messageI add a return statement with the name of the input
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 'I got: value'the terminal is my friend, and shows AssertionError
FAILED ...test_passing_none - AssertionError: assert 'I got: value' == 'I got: None' FAILED ...test_passing_booleans - AssertionError: assert 'I got: value' == 'I got: False' FAILED ...test_passing_an_integer - AssertionError: assert 'I got: value' == 'I got: 1234' FAILED ...test_passing_a_float - AssertionError: assert 'I got: value' == 'I got: 5.678' FAILED ...test_passing_a_string - AssertionError: assert 'I got: value' == 'I got: hello' FAILED ...test_passing_a_tuple - assert 'I got: value' == "I got: (0, 1, 2, 'n')" FAILED ...test_passing_a_list - assert 'I got: value' == "I got: [0, 1, 2, 'n']" FAILED ...test_passing_a_set - assert 'I got: value' == "I got: {0, 1, 2, 'n'}" FAILED ...test_passing_a_dictionary - assert 'I got: value' == "I got: {'key..., 1, 2, 'n']}" FAILED ...test_passing_a_class - assert 'I got: value' == "I got: <class 'object'>"I change the return statement to 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 'I got: value' 9 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
src/telephone/__init__.pyI 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
telephonecd ..the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory.
review
code from the 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.