functions: test_functions_w_positional_and_keyword_arguments


test_functions_w_positional_and_keyword_arguments

red: make it fail

I can also define functions to take both positional arguments and keyword arguments as inputs. I add a new failing test to test_functions.py

def test_functions_w_positional_and_keyword_arguments(self):
    self.assertEqual(
    functions.takes_positional_and_keyword_arguments(
        last_name='my_last_name', 'my_first_name'
    ),
    {}
  )

the terminal shows a SyntaxError because I put a positional argument after a keyword argument. I add the error to the list of Exceptions encountered

# Exceptions Encountered
# AssertionError
# ModuleNotFoundError
# AttributeError
# TypeError
# SyntaxError

green: make it pass

  • I fix the order of arguments in test_functions_w_positional_and_keyword_arguments since keyword arguments come after positional arguments

    def test_functions_w_positional_and_keyword_arguments(self):
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(
                'my_first_name', last_name='my_last_name'
            ),
            {}
        )
    

    the terminal shows AttributeError

  • I add a definition for the function to functions.py

    def takes_positional_and_keyword_arguments():
        return None
    

    the terminal shows TypeError

    TypeError: takes_positional_and_keyword_arguments() got an unexpected keyword argument 'last_name'
    
  • I make the function signature to take in an argument

    def takes_positional_and_keyword_arguments(last_name):
        return None
    

    the terminal shows another TypeError

    TypeError: takes_positional_and_keyword_arguments() got multiple values for argument 'last_name'
    
  • I add another argument to the function signature

    def takes_positional_and_keyword_arguments(last_name, first_name):
        return None
    

    the terminal shows the same error even though I have 2 different arguments. I need a way to let the takes_positional_and_keyword_arguments know which argument is positional and which is a keyword argument

  • I reorder the arguments in the signature

    def takes_positional_and_keyword_arguments(first_name, last_name):
        return None
    

    the terminal shows AssertionError

  • I edit the return statement to make the test pass

    def takes_positional_and_keyword_arguments(first_name, last_name):
        return first_name, last_name
    

    the terminal changes the AssertionError with the values I just added

  • I make test_functions_w_positional_and_keyword_arguments to make the results match the expectation

    def test_functions_w_positional_and_keyword_arguments(self):
        self.assertEqual(
        functions.takes_positional_and_keyword_arguments(
                'my_first_name', last_name='my_last_name'
            ),
            ('my_first_name', 'my_last_name')
        )
    

    the terminal shows passing tests

refactor: make it better

Hold on a second. This looks exactly like what I did in test_functions_w_positional_arguments. I cannot tell from the function signature which argument is positional and which is a keyword argument and do not want to wait for the function to fail when I send in values to find out

  • I make the signature of takes_positional_and_keyword_arguments to have a default value for the keyword argument

    def takes_positional_and_keyword_arguments(first_name, last_name=None):
        return first_name, last_name
    

    all tests are still passing

  • I did not add a default argument for first_name, what would happen if I did?

    def takes_positional_and_keyword_arguments(first_name=None, last_name=None):
        return first_name, last_name
    

    I still have passing tests. It looks like Python lets us use default arguments with no issues, and I can provide keyword arguments positionally without using the name.

  • I add another test to test_functions_w_positional_and_keyword_arguments to show this

    def test_functions_w_positional_and_keyword_arguments(self):
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(
                'my_first_name', last_name='my_last_name'
            ),
            ('my_first_name', 'my_last_name')
        )
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(
                'my_first_name', 'my_last_name'
            ),
            ('my_first_name', 'my_last_name')
        )
    

    all the tests are still passing. The problem here is without the names the program is going to take the input data in the order I provide it so it is better to be explicit with the names, from the Zen of Python : Explicit is better than implicit.

  • I add 2 tests, this time for an unknown number of positional and keyword arguments

    def test_functions_w_positional_and_keyword_arguments(self):
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(
                'my_first_name', last_name='my_last_name'
            ),
            ('my_first_name', 'my_last_name')
        )
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(
                'my_first_name', 'my_last_name'
            ),
            ('my_first_name', 'my_last_name')
        )
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(),
            (None, None)
        )
        self.assertEqual(
            functions.takes_positional_and_keyword_arguments(
                bool, int, float, str, tuple, list, set, dict,
                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 TypeError because the function signature only has 2 keyword arguments which are not provided in the call

  • using what I know from previous tests I can alter the function to use starred expressions

    def takes_positional_and_keyword_arguments(*args, **kwargs):
        return args, kwargs
    

    the terminal shows AssertionError for a previous passing test. I have introduced a regression

    E   AssertionError: Tuples differ: (('my_first_name',), {'last_name': 'my_last_name'}) != ('my_first_name', 'my_last_name')
    
  • I comment out the other assertions so I can focus on the failing test

    def test_functions_w_positional_and_keyword_arguments(self):
        self.assertEqual(
          functions.takes_positional_and_keyword_arguments(
            'my_first_name', last_name='my_last_name'
          ),
          ('my_first_name', 'my_last_name')
        )
        # self.assertEqual(
        #    functions.takes_positional_and_keyword_arguments(
        #        'my_first_name', 'my_last_name'
        #    ),
        #    (('my_first_name', 'last_name'), {})
        # )
        # self.assertEqual(
        #     functions.takes_positional_and_keyword_arguments(),
        #     (None, None)
        # )
        # self.assertEqual(
        #    functions.takes_positional_and_keyword_arguments(
        #        bool, int, float, str, tuple, list, set, dict,
        #        a_boolean=bool, an_integer=int, a_float=float,
        #        a_string=str, a_tuple=tuple, a_list=list,
        #        a_set=set, a_dictionary=dict
        #    ),
        #    ()
        # )
    
  • I change the expected values in the test to make it pass

    self.assertEqual(
        functions.takes_positional_and_keyword_arguments(
            'my_first_name', last_name='my_last_name'
        ),
        (('my_first_name',), {'last_name': 'my_last_name'})
    )
    

    the terminal shows tests passing, with the positional argument in parentheses and the keyword argument in curly braces

  • I uncomment the next test

    self.assertEqual(
        functions.takes_positional_and_keyword_arguments(
            'my_first_name', 'my_last_name'
        ),
        (('my_first_name', 'last_name'), {})
    )
    

    the terminal shows AssertionError

    E    AssertionError: Tuples differ: (('my_first_name', 'my_last_name'), {}) != (('my_first_name', 'last_name'), {})
    
  • I make the test pass with both positional arguments in parentheses and empty curly braces since there are no keyword arguments

    self.assertEqual(
        functions.takes_positional_and_keyword_arguments(
            'my_first_name', 'my_last_name'
        ),
        (('my_first_name', 'my_last_name'), {})
    )
    

    and the terminal shows passing tests

  • I uncomment the next test to see it fail

    self.assertEqual(
        functions.takes_positional_and_keyword_arguments(),
        (None, None)
    )
    

    the terminal shows AssertionError

    AssertionError: Tuples differ: ((), {}) != (None, None)
    
  • I make the test pass with empty parentheses and curly braces as the expectation since no positional or keyword arguments were provided as inputs

    self.assertEqual(
        functions.takes_positional_and_keyword_arguments(),
        ((), {})
    )
    
  • I uncomment the last test to see it fail and the terminal shows AssertionError

    AssertionError: Tuples differ: ((<class 'bool'>, <class 'int'>, <class 'f[307 chars]t'>}) != ()
    
  • I make the test pass

    self.assertEqual(
        functions.takes_positional_and_keyword_arguments(
            bool, int, float, str, tuple, list, set, dict,
            a_boolean=bool, an_integer=int, a_float=float,
            a_string=str, a_tuple=tuple, a_list=list,
            a_set=set, a_dictionary=dict
        ),
        (
            (bool, int, float, str, tuple, list, set, dict,),
            {
                'a_boolean': bool,
                'an_integer': int,
                'a_float': float,
                'a_string': str,
                'a_tuple': tuple,
                'a_list': list,
                'a_set': set,
                'a_dictionary': dict
            }
        )
    )
    

review

From the tests I know that

  • I can define default values for arguments

  • positional arguments must come before keyword arguments

  • 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

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

  • that passthrough functions return what they receive as input

  • that singleton functions return the same thing every time they are called

  • functions are defined using the def keyword

  • functions return None by default

Would you like to test classes?


functions: tests and solutions