how to make a calculator 1
preview
These are the tests I have by the end of the chapter
start the project
I name this project
calculatorI open a terminal
I make a directory for the project
mkdir calculatorthe terminal goes back to the command line
I change directory to the project
cd calculatorthe terminal shows I am in the
calculatorfolder.../pumping_python/calculatorI make a directory for the source code
mkdir srcthe terminal goes back to the command line
I make a Python file to hold the source code in the
srcdirectorytouch src/calculator.pyNote
on Windows without Windows Subsystem for Linux use
New-Item src/calculator.pyinstead oftouch src/calculator.pyNew-Item src/calculator.pythe terminal goes back to the command line
I make a directory for the tests
mkdir teststhe terminal goes back to the command line
I make the
testsdirectory a Python packageDanger
use 2 underscores (__) before and after
initfor__init__.pynot_init_.pytouch tests/__init__.pyNote
on Windows without Windows Subsystem for Linux use
New-Item tests/__init__.pyinstead oftouch tests/__init__.pyNew-Item tests/__init__.pythe terminal goes back to the command line
I make a Python file for the tests in the
testsdirectorytouch tests/test_calculator.pyNote
on Windows without Windows Subsystem for Linux use
New-Item tests/test_calculator.pyinstead oftouch tests/test_calculator.pyNew-Item tests/test_calculator.pythe terminal goes back to the command line
I open
test_calculator.pyin the editor of the Integrated Development Environment (IDE)Tip
I can open a file from the terminal in the Integrated Development Environment (IDE) with the name of the program and the name of the file. That means if I type this in the terminal
code tests/test_calculator.pyVisual Studio Code opens
test_calculator.pyin the editorI add the first failing test to
test_calculator.py1import unittest 2 3 4class TestCalculator(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(True)I make a requirements file for the Python packages I need, in the terminal
echo "pytest" > requirements.txtthe terminal goes back to the command line
I add pytest-watcher to the file
echo "pytest-watcher" >> requirements.txtthe terminal goes back to the command line
I setup the project with uv
uv initthe terminal shows
Initialized project `calculator`I remove
main.pyfrom the project because I do not use itrm main.pythe terminal goes back to the command line
I install the Python packages I gave in the requirements file
uv add --requirement requirements.txtthe terminal shows it installed the Python packages
I use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal shows
================================ FAILURES ================================ _____________________ TestCalculator.test_failure ________________________ self = <tests.test_calculator.TestCalculator testMethod=test_failure> def test_failure(self): > self.assertFalse(True) E AssertionError: True is not false tests/test_calculator.py:7: AssertionError ======================== short test summary info ========================= FAILED tests/test_calculator.py::TestCalculator::test_failure - AssertionError: True is not false =========================== 1 failed in X.YZs ============================I hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and use the mouse to click on
tests/test_calculator.py:7to put the cursor on line 7 in the editorI add AssertionError to the list of Exceptions seen in
test_calculator.py4class TestCalculator(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(True) 8 9 10# Exceptions seen 11# AssertionErrorthen I change True to False in the assertion
7 self.assertFalse(False)the test passes
I add a TODO list to keep track of the work for the program
1import unittest 2 3 4class TestCalculator(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(False) 8 9 10# TODO 11# test addition 12# test subtraction 13# test multiplication 14# test division 15 16 17# Exceptions seen 18# AssertionError
test_addition
RED: make it fail
I change
test_failuretotest_additionthen change assertFalse to assertEqual1import unittest 2 3 4class TestCalculator(unittest.TestCase): 5 6 def test_addition(self): 7 self.assertEqual(src.calculator.add(0, 1), 1)the assertEqual method from AssertionError checks if the 2 things in parentheses are the same. It is like the statement
assert x == yor askingis x equal to y?I think of
self.assertEqual(src.calculator.add(0, 1), 1)as
self.assertEqual(reality, my_expectation)where
realityissrc.calculator.add(0, 1)my_expectationis1because0 + 1is1
in other words,
self.assertEqual(src.calculator.add(0, 1), 1)checks if the result of callingsrc.calculator.addwith0and1as input is equal to1
NameError: name 'src' is not definedbecause I do not have a definition for
srcintest_calculator.py
GREEN: make it pass
I add NameError to the list of Exceptions seen in
test_calculator.py17# Exceptions seen 18# AssertionError 19# NameErrorI add an import statement at the top of the file for the
calculatormodule1import src.calculator 2import unittest 3 4 5class TestCalculator(unittest.TestCase):the terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'add'I think of
src.calculator.addas an address,addis something (an attribute) in the emptycalculator.pyfile from thesrcfolderI add AttributeError to the list of Exceptions seen in
test_calculator.py18# Exceptions seen 19# AssertionError 20# NameError 21# AttributeErrorI open
calculator.pyfrom thesrcfolder in the editor, then add the name1addNameError: name 'add' is not definedI have to tell Python what the name
addmeansI point it to None to define it
1add = NoneTypeError: 'NoneType' object is not callablebecause the
addvariable is now a name for None, and I cannot call None like a functionI add TypeError to the list of Exceptions seen in
test_calculator.py18# Exceptions seen 19# AssertionError 20# NameError 21# AttributeError 22# TypeErrorI use the def keyword in
calculator.pyto makeadda function so it is callable1def add(): 2 return NoneTypeError: add() takes 0 positional arguments but 2 were giventhe definition of
adddoes not allow it take input, but 2 were given in the call -0and1I add names in the parentheses to make the function take 2 inputs
1def add(first_input, second_input): 2 return Nonethe terminal shows AssertionError
AssertionError: None != 1I make the return statement give the test what it wants
1def add(first_input, second_input): 2 return 1the test passes, time for a victory lap!
REFACTOR: make it better
The add function passes the test but does not do what I actually want because it always returns 1. I want it to return the result of a calculation with the inputs
I add another assertion to show the problem with the function in
test_calculator.py7 def test_addition(self): 8 self.assertEqual(src.calculator.add(0, 1), 1) 9 self.assertEqual(src.calculator.add(0, 2), 2)the terminal shows AssertionError
AssertionError: 1 != 2the function returns
1, the test expects2I change the return statement in
calculator.py1def add(first_input, second_input): 2 return 2the terminal shows AssertionError
AssertionError: 2 != 1this makes the assertion that was passing before -
src.calculator.add(0, 1)fail. If the test sends0and2tosrc.calculator.addit returns20and1tosrc.calculator.addit returns2
I need a solution that can make the two tests pass. The function should return
2if the test sends0and2tosrc.calculator.addbecause0 + 2 == 21if the test sends0and1tosrc.calculator.addbecause0 + 1 == 1
I make the function return the result of adding the two inputs
1def add(first_input, second_input): 2 return first_input + second_inputthe two tests are passing
I add another test to make sure the function works for other numbers in
test_calculator.py7 def test_addition(self): 8 self.assertEqual(src.calculator.add(0, 1), 1) 9 self.assertEqual(src.calculator.add(0, 2), 2) 10 self.assertEqual(src.calculator.add(0, 3), 2)the terminal shows AssertionError
AssertionError: 3 != 2I change the expectation in the test
10 self.assertEqual(src.calculator.add(0, 3), 3)the test passes
I add another test with a different number for the first input
7 def test_addition(self): 8 self.assertEqual(src.calculator.add(0, 1), 1) 9 self.assertEqual(src.calculator.add(0, 2), 2) 10 self.assertEqual(src.calculator.add(0, 3), 3) 11 self.assertEqual(src.calculator.add(1, 3), 3)the terminal shows AssertionError
AssertionError: 4 != 3I change the expectation to match reality
11 self.assertEqual(src.calculator.add(1, 3), 4)the test passes. The
addfunction looks good so far.I add an assertion with bigger numbers
7 def test_addition(self): 8 self.assertEqual(src.calculator.add(0, 1), 1) 9 self.assertEqual(src.calculator.add(0, 2), 2) 10 self.assertEqual(src.calculator.add(0, 3), 3) 11 self.assertEqual(src.calculator.add(1, 3), 4) 12 self.assertEqual(src.calculator.add(123456, 789012), 4)the terminal shows AssertionError
AssertionError: 912468 != 4I change the expectation to match reality
12 self.assertEqual(src.calculator.add(123456, 789012), 912468)the test passes
I add another assertion with a negative number
7 def test_addition(self): 8 self.assertEqual(src.calculator.add(0, 1), 1) 9 self.assertEqual(src.calculator.add(0, 2), 2) 10 self.assertEqual(src.calculator.add(0, 3), 3) 11 self.assertEqual(src.calculator.add(1, 3), 4) 12 self.assertEqual(src.calculator.add(123456, 789012), 912468) 13 self.assertEqual(src.calculator.add(-1, 0), 912468)the terminal shows AssertionError
AssertionError: -1 != 912468I change the expectation to match reality
13 self.assertEqual(src.calculator.add(-1, 0), -1)the test passes
I try another assertion with two negative numbers
11 self.assertEqual(src.calculator.add(1, 3), 4) 12 self.assertEqual(src.calculator.add(123456, 789012), 912468) 13 self.assertEqual(src.calculator.add(-1, 0), -1) 14 self.assertEqual(src.calculator.add(-2, -3), -1)the terminal shows AssertionError
AssertionError: -5 != -1I make the expectation match reality
14 self.assertEqual(src.calculator.add(-2, -3), -5)the test passes. The
addfunction can handle positive and negative whole numbersI add an assertion with floats (binary floating point decimal numbers)
12 self.assertEqual(src.calculator.add(123456, 789012), 912468) 13 self.assertEqual(src.calculator.add(-1, 0), -1) 14 self.assertEqual(src.calculator.add(-2, -3), -5) 15 self.assertEqual(src.calculator.add(0.1, 1), -5)the terminal shows AssertionError
AssertionError: 1.1 != -5I change the expectation
15 self.assertEqual(src.calculator.add(0.1, 1), 1.1)the test passes
I add another assertion
13 self.assertEqual(src.calculator.add(-1, 0), -1) 14 self.assertEqual(src.calculator.add(-2, -3), -5) 15 self.assertEqual(src.calculator.add(0.1, 1), 1.1) 16 self.assertEqual(src.calculator.add(0.1, 0.2), 1.1)the terminal shows AssertionError
AssertionError: 0.30000000000000004 != 1.1whoa!
I change the expectation
16 self.assertEqual( 17 src.calculator.add(0.1, 0.2), 18 0.30000000000000004 19 )the test passes. Why is the result “0.30000000000000004” and not “0.3”?
I add another assertion
14 self.assertEqual(src.calculator.add(-2, -3), -5) 15 self.assertEqual(src.calculator.add(0.1, 1), 1.1) 16 self.assertEqual( 17 src.calculator.add(0.1, 0.2), 18 0.30000000000000004 19 ) 20 self.assertEqual( 21 src.calculator.add(0.1234, -5.6789), 22 0.30000000000000004 23 )the terminal shows AssertionError
AssertionError: -5.555499999999999 != -5whaaaaat?!
I change the expectation
7 def test_addition(self): 8 self.assertEqual(src.calculator.add(0, 1), 1) 9 self.assertEqual(src.calculator.add(0, 2), 2) 10 self.assertEqual(src.calculator.add(0, 3), 3) 11 self.assertEqual(src.calculator.add(1, 3), 4) 12 self.assertEqual(src.calculator.add(123456, 789012), 912468) 13 self.assertEqual(src.calculator.add(-1, 0), -1) 14 self.assertEqual(src.calculator.add(-2, -3), -5) 15 self.assertEqual(src.calculator.add(0.1, 1), 1.1) 16 self.assertEqual( 17 src.calculator.add(0.1, 0.2), 18 0.30000000000000004 19 ) 20 self.assertEqual( 21 src.calculator.add(0.1234, -5.6789), 22 -5.555499999999999 23 ) 24 25 26# TODOthe test passes. Why is the result “-5.555499999999999” not “-5.5555”?
what is a variable?
I just did the same kind of calculation 10 times, there is a better way to do this thanks to Substitution. I can use a letter or a name for the numbers, so that I can have one test for all possible numbers, it is called a variable.
A variable is a name that is used for values that change. For example, in the tests so far, I have
src.calculator.add(0, 1) is 0 + 1 is 1
src.calculator.add(0, 2) is 0 + 2 is 2
src.calculator.add(0, 3) is 0 + 3 is 3
src.calculator.add(1, 3) is 1 + 3 is 4
src.calculator.add(123456, 789012) is 123456 +789012 is 912468
src.calculator.add(-2, -3) is -2 + -3 is -5
src.calculator.add(-1, 0) is -1 + 0 is -1
src.calculator.add(0.1, 1) is 0.1 + 1 is 1.1
src.calculator.add(0.1, 0.2) is 0.1 + 0.2 is 0.30000000000000004
src.calculator.add(0.1234, -5.6789) is 0.1234 + -5.6789 is -5.555499999999999
all of these lines can be written using x as the name of the first number and y as the name for the second number, like this
src.calculator.add(x, y) is x + y is x + y
I add names to the first assertion
7 def test_addition(self): 8 first_number = 0 9 second_number = 1 10 self.assertEqual( 11 src.calculator.add(first_number, second_number), 12 first_number+second_number 13 ) 14 self.assertEqual(src.calculator.add(0, 2), 2)the test is still green
I do the same thing to the next assertion
7 def test_addition(self): 8 first_number = 0 9 second_number = 1 10 self.assertEqual( 11 src.calculator.add(first_number, second_number), 12 first_number+second_number 13 ) 14 15 first_number = 0 16 second_number = 2 17 self.assertEqual( 18 src.calculator.add(first_number, second_number), 19 first_number+second_number 20 )still green
I do the next one
15 first_number = 0 16 second_number = 2 17 self.assertEqual( 18 src.calculator.add(first_number, second_number), 19 first_number+second_number 20 ) 21 22 first_number = 0 23 second_number = 3 24 self.assertEqual( 25 src.calculator.add(first_number, second_number), 26 first_number+second_number 27 )green
then the next one
22 first_number = 0 23 second_number = 3 24 self.assertEqual( 25 src.calculator.add(first_number, second_number), 26 first_number+second_number 27 ) 28 29 first_number = 1 30 second_number = 3 31 self.assertEqual( 32 src.calculator.add(first_number, second_number), 33 first_number+second_number 34 )still green
then the next
29 first_number = 1 30 second_number = 3 31 self.assertEqual( 32 src.calculator.add(first_number, second_number), 33 first_number+second_number 34 ) 35 36 first_number = 123456 37 second_number = 789012 38 self.assertEqual( 39 src.calculator.add(first_number, second_number), 40 first_number+second_number 41 )the test is still green
on to the next assertion
36 first_number = 123456 37 second_number = 789012 38 self.assertEqual( 39 src.calculator.add(first_number, second_number), 40 first_number+second_number 41 ) 42 43 first_number = -1 44 second_number = 0 45 self.assertEqual( 46 src.calculator.add(first_number, second_number), 47 first_number+second_number 48 )still green
and the next
43 first_number = -1 44 second_number = 0 45 self.assertEqual( 46 src.calculator.add(first_number, second_number), 47 first_number+second_number 48 ) 49 50 first_number = -2 51 second_number = -3 52 self.assertEqual( 53 src.calculator.add(first_number, second_number), 54 first_number+second_number 55 )green
more?
50 first_number = -2 51 second_number = -3 52 self.assertEqual( 53 src.calculator.add(first_number, second_number), 54 first_number+second_number 55 ) 56 57 first_number = 0.1 58 second_number = 1 59 self.assertEqual( 60 src.calculator.add(first_number, second_number), 61 first_number+second_number 62 )still green
I add variables to the next assertion
57 first_number = 0.1 58 second_number = 1 59 self.assertEqual( 60 src.calculator.add(first_number, second_number), 61 first_number+second_number 62 ) 63 64 first_number = 0.1 65 second_number = 0.2 66 self.assertEqual( 67 src.calculator.add(first_number, second_number), 68 first_number+second_number 69 )the test is still green
ah, the last one
64 first_number = 0.1 65 second_number = 0.2 66 self.assertEqual( 67 src.calculator.add(first_number, second_number), 68 first_number+second_number 69 ) 70 71 first_number = 0.1234 72 second_number = -5.6789 73 self.assertEqual( 74 src.calculator.add(first_number, second_number), 75 first_number+second_number 76 ) 77 78 79# TODOstill green
how to use random numbers
All the assertions in test_addition do the same thing
point
first_numberto a valuepoint
second_numberto a valuecall
src.calculator.addwithfirst_numberandsecond_numberas inputcheck if the result of
src.calculator.add(first_number, second_number)is the same asfirst_number + second_number
I want to use random numbers for first_number and second_number to make sure that the add function always returns the result of adding the two numbers without knowing what the numbers are.
I can do this with the random module from The Python Standard Library, it is a module from The Python Standard Library that is used to make fake random numbers
I add an import statement for the random module at the top of
test_calculator.py1import random 2import src.calculator 3import unittestI use a random value for
first_numberin the first assertion8 def test_addition(self): 9 # first_number = 0 10 first_number = random.triangular(-0.1, 1.0) 11 second_number = 1 12 self.assertEqual( 13 src.calculator.add(first_number, second_number), 14 first_number+second_number 15 )green. random.triangular returns a random float that could be any number from
-0.1to1.0in this case, I can also use random.randint if I want a random integerI want to see the test fail to be sure everything works as expected. I change the expectation in the first assertion
8 def test_addition(self): 9 # first_number = 0 10 first_number = random.triangular(-0.1, 1.0) 11 second_number = 1 12 self.assertEqual( 13 src.calculator.add(first_number, second_number), 14 first_number+first_number 15 )I use (ctrl+s (Windows/Linux) or command+s (MacOS)) a few times in the editor to run the tests and the terminal shows AssertionError with random values that look like this
AssertionError: -X.YZABCDEFGHIJKLM != A.BCDEFGHIJKLMNOPQthe letters are for random numbers
I change the expectation of the assertion back to the right calculation
12 self.assertEqual( 13 src.calculator.add(first_number, second_number), 14 first_number+second_number 15 )the test is green again
I remove the commented line then use a random value for
second_number8 def test_addition(self): 9 first_number = random.triangular(-0.1, 1.0) 10 # second_number = -5.6789 11 second_number = random.triangular(-0.1, 1.0)the test is still green
I remove the commented line and the other assertions because they are covered by the one that uses random numbers. I do not need them anymore
8 def test_addition(self): 9 first_number = random.triangular(-0.1, 1.0) 10 second_number = random.triangular(-0.1, 1.0) 11 self.assertEqual( 12 src.calculator.add(first_number, second_number), 13 first_number+second_number 14 ) 15 16 17# TODOstill green
I use the
Rename Symbolfeature to change the name of variables to say what they are8def test_addition(self): 9 random_first_number = random.triangular(-0.1, 1.0) 10 random_second_number = random.triangular(-0.1, 1.0) 11 12 self.assertEqual( 13 src.calculator.add( 14 random_first_number, 15 random_second_number 16 ), 17 random_first_number+random_second_number 18 )green
There is some duplication, If I want to use a different range of random numbers for the test, I have to make a change in more than one place. For example
8 def test_addition(self): 9 random_first_number = random.triangular(-10.0, 10.0) 10 random_second_number = random.triangular(-10.0, 10.0)still green
I add a function to remove the repetition
1import random 2import src.calculator 3import unittest 4 5 6def a_random_number(): 7 return random.triangular(-10.0, 10.0) 8 9 10class TestCalculator(unittest.TestCase):I use the new function to get random values for the
random_first_numberandrandom_second_numbervariables12 def test_addition(self): 13 # random_first_number = random.triangular(-10.0, 10.0) 14 random_first_number = a_random_number() 15 # random_second_number = random.triangular(-10.0, 10.0) 16 random_second_number = a_random_number()the test is still green
I remove the commented lines
12 def test_addition(self): 13 random_first_number = a_random_number() 14 random_second_number = a_random_number()I change the expectation in the assertion to make sure the test works
16 self.assertEqual( 17 src.calculator.add( 18 random_first_number, 19 random_second_number 20 ), 21 random_second_number+random_second_number 22 )the terminal shows AssertionError
AssertionError: R.STUVWXYZABCDEFG != H.IJKLMNOPQRSTUVWXI undo the change
16 def test_addition(self): 17 random_first_number = a_random_number() 18 random_second_number = a_random_number() 19 20 self.assertEqual( 21 src.calculator.add( 22 random_first_number, 23 random_second_number 24 ), 25 random_first_number+random_second_number 26 ) 27 28 29# TODOthe test is green again
I now only need to change the range of random numbers for the test in one place
6def a_random_number(): 7 return random.triangular(-10000.0, 10000.0)the test is still green
I can use any range of numbers the computer can handle, for example
6def a_random_number(): 7 return random.triangular(-10.0**100000, 10.0**100000)the terminal shows OverflowError
OverflowError: (34, 'Numerical result out of range')because the numbers are too big
**is the symbol for raise to the power (exponents)10.0**100000is how to write10.0raised to the power of100,000
I make the range smaller
6def a_random_number(): 7 return random.triangular(-1000.0, 1000.0)the test is still green, though the test takes a little longer to run
I remove
test additionfrom the TODO list22# TODO 23# test subtraction 24# test multiplication 25# test division 26 27 28# Exceptions seen
test_subtraction
RED: make it fail
I add a test for subtraction in test_calculator.py
12 def test_addition(self):
13 random_first_number = a_random_number()
14 random_second_number = a_random_number()
15
16 self.assertEqual(
17 src.calculator.add(
18 random_first_number,
19 random_second_number
20 ),
21 random_first_number+random_second_number
22 )
23
24 def test_subtraction(self):
25 random_first_number = a_random_number()
26 random_second_number = a_random_number()
27
28 self.assertEqual(
29 src.calculator.subtract(
30 random_first_number,
31 random_second_number
32 ),
33 random_first_number-random_second_number
34 )
35
36
37# TODO
the terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'subtract'
calculator.py in the src folder does not have anything named subtract in it
GREEN: make it pass
I add the name to
calculator.py1def add(first_input, second_input): 2 return first_input + second_input 3 4 5subtractNameError: name 'subtract' is not definedI point
subtractto None5subtract = NoneTypeError: 'NoneType' object is not callableI have seen this before
I change
subtractto a function to make it callable5def subtract(): 6 return NoneTypeError: subtract() takes 0 positional arguments but 2 were givenI make
subtracttake inputs5def subtract(first_input, second_input): 6 return Nonethe terminal shows AssertionError
AssertionError: None != XYZ.ABCDEFGHIJKLMNOPsubtractreturns None, the test expectsrandom_first_number-random_second_numberwhich isfirst_input-second_inputthe difference between the 2 numbers
I make the
subtractfunction return the difference between the inputs5def subtract(first_input, second_input): 6 return first_input - second_inputthe test passes. SUCCESS!
I remove
test subtractionfrom the TODO list intest_calculator.py37# TODO 38# test multiplication 39# test division 40 41 42# Exceptions seen
test_multiplication
RED: make it fail
I add a failing test for multiplication in test_calculator.py
24 def test_subtraction(self):
25 random_first_number = a_random_number()
26 random_second_number = a_random_number()
27
28 self.assertEqual(
29 src.calculator.subtract(
30 random_first_number,
31 random_second_number
32 ),
33 random_first_number-random_second_number
34 )
35
36 def test_multiplication(self):
37 random_first_number = a_random_number()
38 random_second_number = a_random_number()
39
40 self.assertEqual(
41 src.calculator.multiply(
42 random_first_number,
43 random_second_number
44 ),
45 random_first_number*random_second_number
46 )
47
48
49 # TODO
the terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'multiply'
GREEN: make it pass
Using what I know so far, I add a function to
calculator.py5def subtract(first_input, second_input): 6 return first_input - second_input 7 8 9def multiply(first_input, second_input): 10 return first_input * second_inputthe test passes
I remove
test_multiplicationfrom the TODO list intest_calculator.py49# TODO 50# test division 51 52 53# Exceptions seen
Note
*is the symbol for multiplication**is the symbol for raise to the power (exponent)
test_division
RED: make it fail
Time for division. I add a new test to test_calculator.py
36 def test_multiplication(self):
37 random_first_number = a_random_number()
38 random_second_number = a_random_number()
39
40 self.assertEqual(
41 src.calculator.multiply(
42 random_first_number,
43 random_second_number
44 ),
45 random_first_number*random_second_number
46 )
47
48 def test_division(self):
49 random_first_number = a_random_number()
50 random_second_number = a_random_number()
51
52 self.assertEqual(
53 src.calculator.divide(
54 random_first_number,
55 random_second_number
56 ),
57 random_first_number/random_second_number
58 )
59
60
61# TODO
62# test division
the terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'divide'
GREEN: make it pass
I add the function to
calculator.py9def multiply(first_input, second_input): 10 return first_input * second_input 11 12 13def divide(first_input, second_input): 14 return first_input / second_inputthe test passes
I remove the TODO list from
test_calculator.py48 def test_division(self): 49 random_first_number = a_random_number() 50 random_second_number = a_random_number() 51 52 self.assertEqual( 53 src.calculator.divide( 54 random_first_number, 55 random_second_number 56 ), 57 random_first_number/random_second_number 58 ) 59 60 61# Exceptions seen 62# AssertionError 63# NameError 64# AttributeError 65# TypeError
REFACTOR: make it better
I have some repetition to remove, the code below happens in every test, that is 4 times in
test_calculator.pyrandom_first_number = a_random_number() random_second_number = a_random_number()I add class attributes (variables) to remove the repetition and use the same numbers for all the tests
10class TestCalculator(unittest.TestCase): 11 12 random_first_number = a_random_number() 13 random_second_number = a_random_number() 14 15 def test_addition(self):I use the new class attributes in test_addition
15 def test_addition(self): 16 # random_first_number = a_random_number() 17 random_first_number = self.random_first_number 18 # random_second_number = a_random_number() 19 random_second_number = self.random_second_number 20 21 self.assertEqual(the test is still green
I use the class attributes for the variables in the call to
src.calculator.addin the assertion21 self.assertEqual( 22 src.calculator.add( 23 # random_first_number, 24 self.random_first_number, 25 # random_second_number 26 self.random_second_number 27 ), 28 random_first_number+random_second_number 29 )still green
I use the class attributes for the variables in the expectation of the assertion
21 self.assertEqual( 22 src.calculator.add( 23 # random_first_number, 24 self.random_first_number, 25 # random_second_number 26 self.random_second_number 27 ), 28 # random_first_number+random_second_number 29 self.random_first_number+self.random_second_number 30 )green
I remove the commented lines and the
random_first_numberandrandom_second_numbervariables from test_addition15 def test_addition(self): 16 self.assertEqual( 17 src.calculator.add( 18 self.random_first_number, 19 self.random_second_number 20 ), 21 self.random_first_number+self.random_second_number 22 ) 23 24 def test_subtraction(self):still green
I use the new class attributes in test_subtraction
24 def test_subtraction(self): 25 # random_first_number = a_random_number() 26 random_first_number = self.random_first_number 27 # random_second_number = a_random_number() 28 random_second_number = self.random_second_number 29 30 self.assertEqual(the test is still green
I use the class attributes for the variables in the call to
src.calculator.subtractin the assertion30 self.assertEqual( 31 src.calculator.subtract( 32 # random_first_number, 33 self.random_first_number, 34 # random_second_number 35 self.random_second_number 36 ), 37 random_first_number-random_second_number 38 )still green
I use the class attributes for the variables in the expectation of the assertion
30 self.assertEqual( 31 src.calculator.subtract( 32 # random_first_number, 33 self.random_first_number, 34 # random_second_number 35 self.random_second_number 36 ), 37 # random_first_number-random_second_number 38 self.random_first_number-self.random_second_number 39 )green
I remove the commented lines and the
random_first_numberandrandom_second_numbervariables from test_subtraction24 def test_subtraction(self): 25 self.assertEqual( 26 src.calculator.subtract( 27 self.random_first_number, 28 self.random_second_number 29 ), 30 self.random_first_number-self.random_second_number 31 ) 32 33 def test_multiplication(self):still green
I use the new class attributes in test_multiplication
33 def test_multiplication(self): 34 # random_first_number = a_random_number() 35 random_first_number = self.random_first_number 36 # random_second_number = a_random_number() 37 random_second_number = self.random_second_number 38 39 self.assertEqual(the test is still green
I use the class attributes for the variables in the call to
src.calculator.multiplyin the assertion39 self.assertEqual( 40 src.calculator.multiply( 41 # random_first_number, 42 self.random_first_number, 43 # random_second_number 44 self.random_second_number 45 ), 46 random_first_number*random_second_number 47 )still green
I use the class attributes for the variables in the expectation of the assertion
39 self.assertEqual( 40 src.calculator.multiply( 41 # random_first_number, 42 self.random_first_number, 43 # random_second_number 44 self.random_second_number 45 ), 46 # random_first_number*random_second_number 47 self.random_first_number*self.random_second_number 48 )green
I remove the commented lines and the
random_first_numberandrandom_second_numbervariables from test_multiplication33 def test_multiplication(self): 34 self.assertEqual( 35 src.calculator.multiply( 36 self.random_first_number, 37 self.random_second_number 38 ), 39 self.random_first_number*self.random_second_number 40 ) 41 42 def test_division(self):still green
I use the new class attributes in test_division
42 def test_division(self): 43 # random_first_number = a_random_number() 44 random_first_number = self.random_first_number 45 # random_second_number = a_random_number() 46 random_second_number = self.random_second_number 47 48 self.assertEqual(the test is still green
I use the class attributes for the variables in the call to
src.calculator.dividein the assertion48 self.assertEqual( 49 src.calculator.divide( 50 # random_first_number, 51 self.random_first_number, 52 # random_second_number 53 self.random_second_number 54 ), 55 random_first_number/random_second_number 56 )still green
I use the class attributes for the variables in the expectation of the assertion
48 self.assertEqual( 49 src.calculator.divide( 50 # random_first_number, 51 self.random_first_number, 52 # random_second_number 53 self.random_second_number 54 ), 55 # random_first_number/random_second_number 56 self.random_first_number/self.random_second_number 57 )green
I remove the commented lines and the
random_first_numberandrandom_second_numbervariables from test_division42 def test_division(self): 43 self.assertEqual( 44 src.calculator.divide( 45 self.random_first_number, 46 self.random_second_number 47 ), 48 self.random_first_number/self.random_second_number 49 ) 50 51 52# Exceptions seen 53# AssertionError 54# NameError 55# AttributeError 56# TypeErrorstill green
All the tests are passing, though they all look the same, there has to be a better way.
The random_first_number and random_second_number variables are made once as class attributes and used later in each test with self.random_first_number and self.random_second_number, the same way I use unittest.TestCase assert methods like assertEqual with self.assertEqual
test_calculator_tests
Since everything is green, I can write the program that makes the tests pass without looking at the tests
RED: make it fail
I close
test_calculator.pyI delete all the text in
calculator.py, the terminal shows 4 failuresFAILED ... - AttributeError: module 'src.calculator' has no attribute 'add' FAILED ... - AttributeError: module 'src.calculator' has no attribute 'divide' FAILED ... - AttributeError: module 'src.calculator' has no attribute 'multiply' FAILED ... - AttributeError: module 'src.calculator' has no attribute 'subtract' =========================== 4 failed in X.YZs ============================I start with the last AttributeError
AttributeError: module 'src.calculator' has no attribute 'subtract'
What other Exceptions do you think are raised as I go along?
GREEN: make it pass
I add the name to
calculator.py1subtractNameError: name 'subtract' is not definedI point it to None to define it
1subtract = NoneTypeError: 'NoneType' object is not callableI change
subtractto a function1def subtract(): 2 return NoneTypeError: subtract() takes 0 positional arguments but 2 were givenI add positional arguments to the function
1def subtract(first_input, second_input): 2 return Nonethe terminal shows AssertionError with random numbers
AssertionError: None != XYZ.ABCDEFGHIJKLMNI change the return statement to see the difference between the inputs and expected output, remember the identity function?
1def subtract(first_input, second_input): 2 return first_input, second_inputthe terminal shows AssertionError with random numbers that look like this
AssertionError: (XYZ.ABCDEFGHIJKLMN, YZA.BCDEFGHIJKLMNO) != ZAB.CDEFGHIJKLMNOPthe name of the function is
subtractand the test expects the difference between the 2 inputsI make the return statement match the expectation
1def subtract(first_input, second_input): 2 return first_input - second_inputthe terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'multiply'
I add a function
1def subtract(first_input, second_input): 2 return first_input - second_input 3 4 5def multiply(): 6 return NoneTypeError: multiply() takes 0 positional arguments but 2 were givenI add 2 names for the positional arguments
5def multiply(first_input, second_input): 6 return Nonethe terminal shows AssertionError
AssertionError: None != XY.ZABCDEFGHIJKLMI change the return statement to see the difference between the inputs and the expected output, this is the identity function again
5def multiply(first_input, second_input): 6 return first_input, second_inputthe terminal shows AssertionError with random numbers that look like this
AssertionError: (XYZ.ABCDEFGHIJKLMNO, -YZA.BCDEFGHIJKLMNOPQ) != -ZAB.CDEFGHIJKLMNOPQRI change it to the multiplication of the inputs to match the name of the function
5def multiply(first_input, second_input): 6 return first_input * second_inputthe terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'divide'
I add another function
5def multiply(first_input, second_input): 6 return first_input * second_input 7 8 9def divide(first_input, second_input): 10 return first_input, second_inputthe terminal shows AssertionError with random numbers that look like this
AssertionError: (-XYZ.ABCDEFGHIJKLMNO, YZA.BCDEFGHIJKLMNOPQ) != -ZAB.CDEFGHIJKLMNOPQRI change the return statement to give the test what it wants
9def divide(first_input, second_input): 10 return first_input / second_inputthe terminal shows AttributeError
AttributeError: module 'src.calculator' has no attribute 'add'
the return statement of the last 3 functions matched their names, I do the same thing for the new one
1def subtract(first_input, second_input): 2 return first_input - second_input 3 4 5def multiply(first_input, second_input): 6 return first_input * second_input 7 8 9def divide(first_input, second_input): 10 return first_input / second_input 11 12 13def add(first_input, second_input): 14 return first_input + second_inputand all the tests are passing with no random failures, or are they?
close the project
review
I wrote these tests for a program that can add, subtract, multiply and divide
I also saw these Exceptions
code from the chapter
what is next?
you know a lot, you know
Would you like to see a better way to write test_why_use_a_function?