test_functions¶
test_functions_w_pass¶
red: make it fail¶
I open a terminal to run makePythonTdd.sh with
functions
as the name of the project./makePythonTdd.sh functions
on Windows without Windows Subsystem Linux use makePythonTdd.ps1
./makePythonTdd.ps1 functions
it makes the folders and files that are needed, installs packages, runs the first test, and the terminal shows AssertionError
E AssertionError: True is not false tests/test_functions.py:7: AssertionError
I hold
ctrl
(windows/linux) oroption
(mac) on the keyboard and use the mouse to click ontests/test_functions.py:7
to open it in the editorthen change
True
toFalse
and change
test_failure
totest_functions_w_pass
import unittest class TestFunctions(unittest.TestCase): def test_functions_w_pass(self): self.assertIsNone(src.functions.function_w_pass())
the terminal shows NameError
I add it to the list of Exceptions encountered
# Exceptions Encountered # AssertionError # NameError
green: make it pass¶
I make a file called
functions.py
in the project folder and the terminal shows AttributeError , which I add to the list of Exceptions encountered# Exceptions Encountered # AssertionError # ModuleNotFoundError # AttributeError
I add a function definition to
functions.py
def function_w_pass(): pass
and we have a passing test
the test checks if the value of the call to
functions.function_w_pass
is Nonethe function definition simply says pass yet the test passes
pass is a placeholder keyword which allows the function definition to follow Python syntax rules
the test passes because in Python all functions return None by default, like the function has an invisible line that says
return None
test_functions_w_return¶
red: make it fail¶
I add a new failing test to TestFunctions
in test_functions.py
to check that functions always return None
def test_functions_w_return(self):
self.assertIsNone(functions.function_w_return())
the terminal shows AttributeError
green: make it pass¶
I add a new function to functions.py
to make the test pass, this time with a return
statement instead of pass
def function_w_return(self):
return
the terminal shows this test also passes
I defined 2 functions with different statements in their body but they both return the same result, because “in Python all functions return None by default, like the function has an invisible line that says return None
”
test_functions_w_return_none¶
red: make it fail¶
I add one more test to the TestFunctions
class in test_functions.py
to help drive home the point
def test_functions_w_return_none(self):
self.assertIsNone(
functions.function_w_return_none()
)
the terminal shows AttributeError
green: make it pass¶
from the Zen of Python: Explicit is better than implicit.
I add a function definition to functions.py
this time with an explicit return
statement showing the value returned
def function_w_return_none():
return None
and the terminal shows passing tests.
test_constant_functions¶
constant functions always return the same thing when called
red: make it fail¶
I add a test to test_functions.py
def test_constant_functions(self):
self.assertEqual(functions.constant(), 'first_name')
the terminal shows AttributeError
green: make it pass¶
I change the function to make it pass
def constant():
return 'first_name'
test_constant_functions_w_inputs¶
red: make it fail¶
I add a new test for a constant function that takes input
def test_constant_functions_w_inputs(self):
self.assertEqual(
src.functions.constant_w_inputs('Bob', 'James', 'Frank'),
src.functions.constant()
)
self.assertEqual(
functions.constant_w_inputs('a', 2, 'c', 3),
src.functions.constant()
)
the terminal shows AttributeError
green: make it pass¶
and I add a definition for it
def constant_w_inputs(*arguments):
return constant()
the terminal shows passing tests
test_identity_functions¶
identity functions return their input as output
red: make it fail¶
I add a failing test to the TestFunctions
class in test_functions.py
def test_identity_functions(self):
self.assertEqual(functions.identity(False), False)
the terminal shows AttributeError
green: make it pass¶
I add a function definition to
functions.py
def identity(): return None
the terminal shows TypeError
TypeError: identity() takes 0 positional arguments but 1 was given
because the definition for
identity
does not allow inputs and the test sends False as inputI add the error to the list of Exceptions encountered
# Exceptions Encountered # AssertionError # ModuleNotFoundError # AttributeError # TypeError
then I make
identity
infunctions.py
to take 1 positional argumentdef identity(argument): return None
and the terminal shows AssertionError
AssertionError: None != False
because the result of calling
functions.identity
with False as input is None which is not equal to the expected result (False)I change the definition of
identity
to make the test passdef identity(argument): return False
the terminal shows passing tests. I am genius!
refactor: make it better¶
Wait a minute! Something is not quite right here. The definition for a identity function was that it returned the same thing it was given, the test passes when False is given as input, will it still pass when another value is given or will it always return False? Time to write a test
I add a new assertion to
test_identity_functions
def test_identity_functions(self): self.assertEqual(functions.identity(False), False) self.assertEqual(functions.identity(True), True)
the terminal shows AssertionError
AssertionError: False != True
the function returns False instead of True in the second case, I am not all the way genius, yet
I change the definition of
identity
infunctions.py
def identity(argument): return argument
the terminal shows passing tests. I have more confidence that the identity function will return its input.
I add more tests for good measure using the other Python data structures
def test_identity_functions(self): self.assertEqual(functions.identity(False), False) self.assertEqual(functions.identity(True), True) self.assertEqual(functions.identity(None), False) self.assertEqual(functions.identity(int), False) self.assertEqual(functions.identity(str), False) self.assertEqual(functions.identity(tuple), False) self.assertEqual(functions.identity(list), False) self.assertEqual(functions.identity(set), False) self.assertEqual(functions.identity(dict), False)
the terminal shows AssertionError for each line until I make the input match the output, proving that the identity function I have defined returns the input it is given. Hooray! I am genius again
test_functions_w_positional_arguments¶
I can make a function take more than one input
def test_functions_w_positional_arguments(self):
self.assertEqual(
functions.function_w_positional_arguments(
'first_name', 'last_name'
),
('first_name', 'last_name')
)
the terminal shows AttributeError
AttributeError: module 'src.functions' has no attribute 'function_w_positional_arguments'
green: make it pass¶
I add a function to
functions.py
def function_w_positional_arguments(argument): return argument
the terminal shows TypeError
TypeError: function_w_positional_arguments() takes 1 positional argument but 2 were given
I make the function take more than one argument
def function_w_positional_arguments( argument, second ): return argument
the terminal shows AssertionError
AssertionError: None != ('first', 'second')
I make it return the 2 arguments it receives
def function_w_positional_arguments( argument, second ): return argument, second
the test passes
refactor: make it better¶
How can I make this better?
I change the name of the first argument to be more descriptive
def function_w_positional_arguments( first, second ): return first, second
I still have passing tests
I add another assertion to make sure that
function_w_positional_arguments
outputs data in the order givendef test_functions_w_positional_arguments(self): self.assertEqual( functions.function_w_positional_arguments( 'first_name', 'last_name' ), ('first_name', 'last_name') ) self.assertEqual( functions.function_w_positional_arguments( 'last_name', 'first_name' ), ('first_name', 'last_name') )
the terminal shows AssertionError
AssertionError: Tuples differ: ('last_name', 'first_name') != ('first_name', 'last_name')
I change the test so it has the right output
def test_functions_w_positional_arguments(self): self.assertEqual( functions.function_w_positional_arguments( 'first_name', 'last_name' ), ('first_name', 'last_name') ) self.assertEqual( functions.function_w_positional_arguments( 'last_name', 'first_name' ), ('last_name', 'first_name') )
the terminal shows passing
the function currently takes in 2 positional arguments.
test_functions_w_unknown_positional_arguments¶
There are scenarios where a function needs to take in more arguments, like when I do not know the number of positional arguments that will be passed to the function, I add a test for the case where the number of positional arguments received is not known
def test_functions_w_unknown_positional_arguments(self): self.assertEqual( src.functions.function_w_unknown_positional_arguments( 0, 1, 2, 3 ), (0, 1, 2, 3) )
the terminal shows AttributeError
AttributeError: module 'src.functions' has no attribute 'function_w_unknown_positional_arguments'. Did you mean: 'function_w_positional_arguments'?
I add the function with a starred expression like I did in test_constant_functions, to allow it take in any number of arguments
def function_w_unknown_positional_arguments(*arguments): return arguments
the test passes
I add another assertion to show that this function never needs to know the number of inputs it is receiving
self.assertEqual( src.functions.function_w_unknown_positional_arguments( 0, 1, 2, 3 ), (0, 1, 2, 3) ) self.assertEqual( src.functions.function_w_unknown_positional_arguments( None, bool, int, float, str, tuple, list, set, dict ), None )
the terminal shows AssertionError
AssertionError: (None, <class 'bool'>, <class 'int'>, <cl[87 chars]ct'>) != None
I change the expectation to match
self.assertEqual( src.functions.function_w_unknown_positional_arguments( None, bool, int, float, str, tuple, list, set, dict ), (None, bool, int, float, str, tuple, list, set, dict) )
the test is green again
test_functions_w_keyword_arguments¶
There is a problem with using positional arguments, the inputs must always be supplied in the right order. which means the program will behave in an unexpected way when it receives input out of order.
To make sure the function behaves how we want regardless of what order the user gives the input I can use Keyword Arguments
red: make it fail¶
I add a new test to test_functions.py
def test_functions_w_keyword_arguments(self):
self.assertEqual(
src.functions.function_w_keyword_arguments(
first_name='first_name',
last_name='last_name',
),
('first_name', 'last_name')
)
the terminal shows AttributeError
AttributeError: module 'src.functions' has no attribute 'function_w_keyword_arguments'. Did you mean: 'function_w_unknown_positional_arguments'?
green: make it pass¶
I add a function definition to
functions.py
def function_w_keyword_arguments(): return None
the terminal shows TypeError
TypeError: function_w_keyword_arguments() got an unexpected keyword argument 'first_name'
I add the argument to the defintion
def function_w_keyword_arguments(first_name): return None
the terminal shows TypeError
TypeError: function_w_keyword_arguments() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?
I add the argument
def function_w_keyword_arguments(first_name, last_name): return None
the terminal shows AssertionError
AssertionError: None != (‘first_name’, ‘last_name’)
I change the return statement
def function_w_keyword_arguments(first_name, last_name): return ('first_name', 'last_name')
the test passes
refactor: make it better¶
So far
function_w_keyword_arguments
looks the same asfunction_w_positional_arguments
, I have not yet seen a difference between apositional argument
and akeyword argument
. I add an assertion that puts the input data out of order to see if there is a differencedef test_functions_w_keyword_arguments(self): self.assertEqual( functions.function_w_keyword_arguments( first_name='first_name', last_name='last_name' ), ('first_name', 'last_name') ) self.assertEqual( src.functions.function_w_keyword_arguments( last_name='last_name', first_name='first_name', ), ('last_name', 'first_name') )
the terminal shows AssertionError
AssertionError: Tuples differ: ('first_name', 'last_name') != ('last_name', 'first_name')
the order stayed the same. I change the expectation to make the test pass
self.assertEqual( src.functions.function_w_keyword_arguments( last_name='last_name', first_name='first_name', ), ('first_name', 'last_name') )
the test passes. Keyword Arguments allow the input to be passed in any order
# ADD examples with dictionary as input
test_functions_w_unknown_keyword_arguments¶
The function currently only takes in 2 keyword arguments. What if I want a function that can take in any number of keyword arguments? There is a starred expression for keyword arguments -
**
. I add an assertiondef test_functions_w_unknown_keyword_arguments(self): self.assertEqual( src.functions.function_w_unknown_keyword_arguments( a=1, b=2, c=3, d=4 ), None )
the terminal shows AttributeError
AttributeError: module 'src.functions' has no attribute 'function_w_unknown_keyword_arguments'. Did you mean: 'function_w_keyword_arguments'?
I add a function using a starred expression
def function_w_unknown_keyword_arguments(*arguments): return arguments
the terminal shows TypeError
TypeError: function_w_unknown_keyword_arguments() got an unexpected keyword argument 'a'
the starred expression for keyword arguments is different, I change the function
def function_w_unknown_keyword_arguments(**keyword_arguments): return keyword_arguments
the terminal shows AssertionError
AssertionError: {'a': 1, 'b': 2, 'c': 3, 'd': 4} != None
I change the expectation in the test to match
def test_functions_w_unknown_keyword_arguments(self): self.assertEqual( src.functions.function_w_unknown_keyword_arguments( a=1, b=2, c=3, d=4 ), {'a': 1, 'b': 2, 'c': 3, 'd': 4} )
the test passes
I add another assertion with a different number of inputs
self.assertEqual( src.functions.function_w_unknown_keyword_arguments( a=1, b=2, c=3, d=4 ), {'a': 1, 'b': 2, 'c': 3, 'd': 4} ) self.assertEqual( src.functions.function_w_unknown_keyword_arguments( none=None, a_boolean=bool, an_integer=int, a_float=float, a_string=str, a_tuple=tuple, a_list=list, a_set=set, a_dictionary=dict ), {} )
the terminal shows AssertionError
AssertionError: {'none': None, 'a_boolean': <class 'bool'>[190 chars]ct'>} != {}
I change the expectation to match the values in the terminal
self.assertEqual( src.functions.function_w_unknown_keyword_arguments( none=None, a_boolean=bool, an_integer=int, a_float=float, a_string=str, a_tuple=tuple, a_list=list, a_set=set, a_dictionary=dict ), dict( a_boolean=bool, a_dictionary=dict, a_float=float, a_list=list, a_set=set, a_string=str, a_tuple=tuple, an_integer=int, none=None, ) )
the test passes
# ADD examples with dictionary as input
review¶
the tests show that
I can use
**name
to represent any number of keyword argumentsthat keyword arguments are represented as dictionaries with curly braces -
{}
I can use
*name
to represent any number of positional argumentspositional arguments are represented as tuples with parentheses -
()
identity functions return their input
constant functions always return the same thing
Would you like to test functions with positional and keyword arguments?