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) or option (mac) on the keyboard and use the mouse to click on tests/test_functions.py:7 to open it in the editor

  • then change True to False

  • and change test_failure to test_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 None

    • the 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 input

  • I add the error to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # ModuleNotFoundError
    # AttributeError
    # TypeError
    
  • then I make identity in functions.py to take 1 positional argument

    def 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 pass

    def 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 in functions.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 given

    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'
            ),
            ('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 as function_w_positional_arguments, I have not yet seen a difference between a positional argument and a keyword argument. I add an assertion that puts the input data out of order to see if there is a difference

    def 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 assertion

    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
            ),
            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 arguments

  • that keyword arguments are represented as dictionaries with curly braces - {}

  • I can use *name to represent any number of positional arguments

  • positional arguments are represented as tuples with parentheses - ()

  • identity functions return their input

  • constant functions always return the same thing

  • functions return None by default

  • functions are defined using the def keyword

Would you like to test functions with positional and keyword arguments?


functions: tests and solutions