how to make a person


This is an exercise in making dictionaries with functions


test_function_w_keyword_arguments

red: make it fail

  • I open a terminal to run makePythonTdd.sh with person as the name of the project

    ./makePythonTdd.sh person
    

    on Windows without Windows Subsystem Linux use makePythonTdd.ps1

    ./makePythonTdd.ps1 person
    

    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_person.py:7: AssertionError
    
  • I hold ctrl (windows/linux) or option (mac) on the keyboard and use the mouse to click on tests/test_person.py:7 to open it in the editor

  • then change True to False to make the test pass

  • and change test_failure to test_function_w_keyword_arguments

    class TestPersonFactory(unittest.TestCase):
    
        def test_function_w_keyword_arguments(self):
            self.assertEqual(
                src.person.factory(),
                None
            )
    

    the terminal shows NameError

    NameError: name 'src' is not defined
    

green: make it pass

  • I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    
  • then I add an import statement for the person module

    import src.person
    import unittest
    

    and get AttributeError

    AttributeError: module 'src.person' has no attribute 'factory'
    
  • I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # AttributeError
    
  • then open person.py from the src folder to add a function

    def factory():
        return None
    

    the terminal shows a passing test

  • I want the function to take in a keyword argument named first_name

    def test_function_w_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name='first_name',
            ),
            None
        )
    

    the terminal shows TypeError

    TypeError: factory() got an unexpected keyword argument 'first_name'
    
  • I add the error to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # AttributeError
    # TypeError
    
  • then add the name as an input parameter to the function

    def factory(first_name):
        return None
    

    and the test passes

  • I want the function to take in a keyword argument named last_name

    def test_function_w_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name='first_name',
                last_name='last_name',
            ),
            None
        )
    

    this gives me TypeError

    TypeError: factory() got an unexpected keyword argument 'last_name'
    
  • when I add the name to the function definition

    def factory(first_name, last_name):
        return None
    

    the terminal shows a passing test

  • I want the function to take in a keyword argument named sex

    def test_function_w_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name='first_name',
                last_name='last_name',
                sex='M',
            ),
            None
        )
    

    the terminal shows TypeError

    TypeError: factory() got an unexpected keyword argument 'sex'
    
  • I add the name as input to the function

    def factory(
            first_name, last_name,
            sex
        ):
        return None
    

    and the terminal shows a passing test

  • I want the function to take in a keyword argument and give it the result of calling another one

    def test_function_w_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name='first_name',
                last_name='last_name',
                sex='M',
                year_of_birth=this_year(),
            ),
            None
        )
    

    the terminal shows NameError

    NameError: name 'this_year' is not defined
    
  • I add the new function above the class definition

    import unittest
    import src.person
    
    
    def this_year():
        return None
    
    
    class TestPersonFactory(unittest.TestCase):
    ..
    

    and get TypeError

    TypeError: factory() got an unexpected keyword argument 'year_of_birth'
    

    when I add the name to the function definition

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return None
    

    the test passes

  • I want the factory function to return a dictionary as output, I change the expectation in the assertion

    def test_function_w_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name='first_name',
                last_name='last_name',
                sex='M',
                year_of_birth=this_year(),
            ),
            dict()
        )
    

    and the terminal shows AssertionError

    AssertionError: None != {}
    

    when I make the return statement match the expectation

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {}
    

    the test passes because {} and dict() do the same thing

  • I want the dictionary to have a key named first_name with the same value as what was given in the call to the factory function

    def test_function_w_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name='first_name',
                last_name='last_name',
                sex='M',
                year_of_birth=this_year(),
            ),
            dict(
                first_name='first_name',
            )
        )
    

    the terminal shows AssertionError

    AssertionError: {} != {'first_name': 'first_name'}
    
  • I copy the value from the right then use it to replace the empty dictionary in the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {'first_name': 'first_name'}
    

    and the test passes

  • 'first_name' appears twice in the test, which means I have to make a change in 2 places if I want a different value for it. I add a variable to remove the repetition

    def test_function_w_keyword_arguments(self):
        first_name = 'first_name'
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name='last_name',
                sex='M',
                year_of_birth=this_year()
            ),
            dict(
                first_name=first_name,
            )
        )
    

    and the test is still green. I now only need to change the value of first_name in one place

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        ...
    

    and the terminal shows AssertionError

    AssertionError: {'first_name': 'first_name'} != {'first_name': 'jane'}
    
  • I copy the value from the terminal then use it to replace the one in the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {'first_name': 'jane}
    

    and the test is green again

  • I want the dictionary to have a key named last_name with the same value as what was given in the call to the factory function

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name='last_name',
                sex='M',
                year_of_birth=this_year()
            ),
            dict(
                first_name=first_name,
                last_name='last_name',
            )
        )
    

    and get AssertionError

    AssertionError: {'first_name': 'jane'} != {'first_name': 'jane', 'last_name': 'last_name'}
    
  • I copy the value from the terminal then use it to change the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'last_name'
        }
    

    and the terminal shows green again

  • 'last_name' happens twice in the test, I add a variable to remove the duplication like I did with first_name

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'last_name'
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name=last_name,
                sex='M',
                year_of_birth=this_year()
            ),
            dict(
                first_name=first_name,
                last_name=last_name,
            )
        )
    

    then change the value

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        ...
    

    and get AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'last_name'} != {'first_name': 'jane', 'last_name': 'doe'}
    
  • I copy the value from the terminal then use it to replace the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': last_name,
        }
    

    and the test passes

  • I add a key named sex to the dictionary with the same value as what was given in the call to the factory function

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name=last_name,
                sex='M',
                year_of_birth=this_year()
            ),
            dict(
                first_name=first_name,
                last_name=last_name,
                sex='M',
            )
        )
    

    the terminal shows AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'}
    

    I copy the value from the right side then use it to replace the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'doe',
            'sex': 'M'
        }
    

    and the terminal shows green again

  • I add a variable to remove the repetition in the test

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        sex = 'M'
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name=last_name,
                sex=sex,
                year_of_birth=this_year()
            ),
            dict(
                first_name=first_name,
                last_name=last_name,
                sex=sex,
            )
        )
    

    still green

  • when I change the value of the sex variable

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        sex = 'F'
        ...
    

    the terminal shows AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
    

    I copy the value from the right side then use it to replace the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'doe',
            'sex': 'F'
        }
    

    and the test is green again

  • I add age to the expectation with a calculation

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        sex = 'F'
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name=last_name,
                sex=sex,
                year_of_birth=this_year(),
            ),
            dict(
                first_name=first_name,
                last_name=last_name,
                sex=sex,
                age=this_year()-this_year(),
            )
        )
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
    

    I cannot do subtraction with None and I want the value for the current year

  • I add an import statement

    import datetime
    import src.person
    import unittest
    

    datetime is a module from the python standard library that is used for dates and times

  • I change the return statement in the this_year function to make it return the current year

    def this_year():
        return datetime.datetime.now().year
    

    datetime.datetime.now().year returns the year attribute of the datetime object returned by the now method of the datetime class, from the datetime module. I can also use the today method to get the same value

    def this_year():
        return datetime.datetime.today().year
    

    the terminal shows AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0}
    

    the new dictionary has a value for age

  • when I copy it from the terminal to replace the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'doe',
            'sex': 'F',
            'age': 0
        }
    

    the test passes

  • I add a variable to remove duplication

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        sex = 'F'
        year_of_birth = this_year()
    
        self.assertEqual(
            src.person.factory(
                first_name=first_name,
                last_name=last_name,
                sex=sex,
                year_of_birth=year_of_birth,
            ),
            dict(
                first_name=first_name,
                last_name=last_name,
                sex=sex,
                age=this_year()-year_of_birth,
            )
        )
    

    and the test is still green

refactor: make it better

  • I add an import statement to use random values

    import datetime
    import random
    import src.person
    import unittest
    

    random is a module from the python standard library that is used to make fake random numbers

  • I use it for the year_of_birth variable

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        sex = 'F'
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
        ...
    

    random.randint(this_year()-120, this_year()) gives me a random number from 120 years ago, up to and including the current year which is returned by this_year(). When the age is not 0, the terminal shows AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 2}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 7}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 14}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 60}
    

    I use the age calculation from the expectation

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'doe',
            'sex': 'F',
            'age': this_year() - year_of_birth,
        }
    

    and get NameError

    NameError: name 'this_year' is not defined
    

    because I called a function that does not exist in person.py. I change the call to the this_year() function to the return statement from test_person.py

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'doe',
            'sex': 'F,
            'age': datetime.datetime.now().year - year_of_birth,
        }
    

    and the terminal shows another NameError

    NameError: name 'datetime' is not defined. Did you forget to import 'datetime'
    

    I add an import statement to person.py

    import datetime
    
    
    def factory(
    ...
    

    and the terminal shows a passing test

  • I add randomness to the sex variable

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = 'doe'
        sex = random.choice(('F', 'M'))
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
        ...
    

    random.choice(('F', 'M')) randomly gives me F or M and the terminal shows random success or AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 56} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 56}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 76} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 76}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 109} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 109}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 115} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 115}
    

    when I change the return statement to use the sex input parameter

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': 'doe',
            'sex': sex,
            'age': datetime.datetime.now().year - year_of_birth,
        }
    

    the test passes with no more random failures

  • I use random.choice with the last_name variable

    def test_function_w_keyword_arguments(self):
        first_name = 'jane'
        last_name = random.choice((
            'doe', 'smith', 'blow', 'public',
        ))
        sex = random.choice(('F', 'M'))
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
        ...
    

    and get random success or AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 51} != {'first_name': 'jane', 'last_name': 'smith', 'sex': 'F', 'age': 51}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 54} != {'first_name': 'jane', 'last_name': 'blow', 'sex': 'M', 'age': 54}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 110} != {'first_name': 'jane', 'last_name': 'public', 'sex': 'F', 'age': 110}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 116} != {'first_name': 'jane', 'last_name': 'public', 'sex': 'M', 'age': 116}
    

    I change the return statement to use the last_name input parameter

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'jane',
            'last_name': last_name,
            'sex': sex,
            'age': datetime.datetime.today().year - year_of_birth,
        }
    

    and the test is green again

  • I do the same thing for the first_name variable

    def test_function_w_keyword_arguments(self):
        first_name = random.choice((
            'jane', 'joe', 'john', 'person',
        ))
        last_name = random.choice((
            'doe', 'smith', 'blow', 'public',
        ))
        sex = random.choice(('F', 'M'))
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
        ...
    

    and get random success or AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'public', 'sex': 'F', 'age': 6} != {'first_name': 'john', 'last_name': 'public', 'sex': 'F', 'age': 6}
    AssertionError: {'first_name': 'jane', 'last_name': 'smith', 'sex': 'M', 'age': 19} != {'first_name': 'person', 'last_name': 'smith', 'sex': 'M', 'age': 19}
    AssertionError: {'first_name': 'jane', 'last_name': 'blow', 'sex': 'M', 'age': 59} != {'first_name': 'person', 'last_name': 'blow', 'sex': 'M', 'age': 59}
    AssertionError: {'first_name': 'jane', 'last_name': 'smith', 'sex': 'F', 'age': 117} != {'first_name': 'joe', 'last_name': 'smith', 'sex': 'F', 'age': 117}
    

    when I change the return statement to use the first_name input parameter

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': last_name,
            'sex': sex,
            'age': datetime.datetime.today().year - year_of_birth,
        }
    

    the terminal shows a passing test.


test_function_w_default_keyword_arguments

I want to see what would happen when I try to make a person without a value for the last_name

red: make it fail

  • I make a copy of test_function_w_keyword_arguments and paste it below

  • then change the name to test_function_w_default_keyword_arguments and remove the last_name variable

    def test_function_w_default_keyword_arguments(self):
        first_name = random.choice((
            'jane', 'joe', 'john', 'person',
        ))
        sex = 'M'
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
        ...
    

    to get NameError

    NameError: name 'last_name' is not defined
    

green: make it pass

  • I remove last_name from the call to the factory function

    self.assertEqual(
        src.person.factory(
            first_name=first_name,
            year_of_birth=year_of_birth,
            sex=sex,
        ),
        dict(
            first_name=first_name,
            last_name=last_name,
            sex=sex,
            age=this_year() - year_of_birth
        )
    )
    

    and get TypeError

    TypeError: factory() missing 1 required positional argument: 'last_name'
    

    the factory function is called with 3 arguments in the test but the definition expects 4

  • I add a default value for last_name

    def factory(
            first_name, last_name=None,
            sex, year_of_birth
        ):
        ...
    

    and the terminal shows a SyntaxError

    SyntaxError: parameter without a default follows parameter with a default
    
  • I add it to the list of Exceptions encountered

    # Exceptions Encountered
    # AssertionError
    # NameError
    # AttributeError
    # TypeError
    # SyntaxError
    
  • then add a default value for the sex parameter

    def factory(
            first_name, last_name=None,
            sex=None, year_of_birth
        ):
        ...
    

    and get another SyntaxError

    SyntaxError: parameter without a default follows parameter with a default
    
  • I give the year_of_birth parameter a default value

    def factory(
            first_name, last_name=None,
            sex=None, year_of_birth=None
        ):
        ...
    

    and the terminal shows NameError

    NameError: name 'last_name' is not defined
    

    the value for the last_name key in the expected dictionary points to a variable that no longer exists

  • I change the expectation for it

    self.assertEqual(
        src.person.factory(
            first_name=first_name,
            sex=sex,
            year_of_birth=year_of_birth,
        ),
        dict(
            first_name=first_name,
            last_name='doe',
            sex=sex,
            age=this_year()-year_of_birth,
        )
    )
    

    and the terminal shows AssertionError

    AssertionError: {'first_name': 'joe', 'last_name': None, 'sex': 'F', 'age': 28} != {'first_name': 'joe', 'last_name': 'doe', 'sex': 'F', 'age': 28}
    AssertionError: {'first_name': 'person', 'last_name': None, 'sex': 'M', 'age': 33} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 33}
    AssertionError: {'first_name': 'jane', 'last_name': None, 'sex': 'F', 'age': 70} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 70}
    AssertionError: {'first_name': 'john', 'last_name': None, 'sex': 'M', 'age': 83} != {'first_name': 'john', 'last_name': 'doe', 'sex': 'M', 'age': 83}
    

    the factory function returns a dictionary with a value of None for last_name and the test expects 'doe'

  • When I make the default value for last_name in the function match the expectation

    def factory(
            first_name, last_name='doe',
            sex, year_of_birth
        ):
        ...
    

    the terminal shows passing tests. When the factory function is called with no value for the last_name argument, it uses 'doe' because that is the default value in the function signature, it is same as calling it with last_name='doe'

    src.person.factory(
        first_name=first_name,
        sex=sex,
        year_of_birth=year_of_birth,
        last_name='doe',
    )
    
  • I remove the sex variable to see what would happen if I do not know its value

    def test_function_w_default_keyword_arguments(self):
        first_name = random.choice((
            'jane', 'joe', 'john', 'person',
        ))
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
        ...
    

    the terminal shows NameError

    NameError: name 'sex' is not defined
    
  • I remove it from the call to the factory function

    self.assertEqual(
        src.person.factory(
            first_name=first_name,
            year_of_birth=year_of_birth,
        ),
        {
            'first_name': first_name,
            'last_name': 'doe',
            'sex': sex,
            'age': this_year()-year_of_birth,
        }
    )
    

    and get the same NameError

    NameError: name 'sex' is not defined
    

    the value in the dictionary still uses the variable I removed. I change the expectation

    self.assertEqual(
        src.person.factory(
            first_name=first_name,
            year_of_birth=year_of_birth,
        ),
        {
            'first_name': first_name,
            'last_name': 'doe',
            'sex': 'M',
            'age': this_year()-year_of_birth,
        }
    )
    

    and get AssertionError

    AssertionError: {'first_name': 'joe', 'last_name': 'doe', 'sex': None, 'age': 4} != {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 4}
    AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': None, 'age': 32} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 32}
    AssertionError: {'first_name': 'john', 'last_name': 'doe', 'sex': None, 'age': 45} != {'first_name': 'john', 'last_name': 'doe', 'sex': 'M', 'age': 45}
    AssertionError: {'first_name': 'person', 'last_name': 'doe', 'sex': None, 'age': 58} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 58}
    

    the factory function returns a dictionary with None as the value for sex and the test expects 'M'

  • when I add a default value to match the expectation

    def factory(
            first_name, last_name='doe',
            sex='M', year_of_birth=None
        ):
        ...
    

    the terminal shows passing tests. When the factory function is called with no value for the sex argument, it uses 'M' because that is the default value in the function signature, it is same as calling it with sex='M'

    src.person.factory(
        first_name=first_name,
        year_of_birth=year_of_birth,
        last_name='doe',
        sex='M',
    )
    

    since the values are the same as the default values, I can call the function without them

    src.person.factory(
        first_name=first_name,
        year_of_birth=year_of_birth,
    )
    

refactor: make it better

  • first_name and year_of_birth are made the same way in both tests, I can remove this repetition with class attributes

    ...
    class TestPerson(unittest.TestCase):
    
        first_name = random.choice((
            'jane', 'joe', 'john', 'person',
        ))
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
    
        ...
    

    then use them in the tests with self. the same way I use the assert methods

    def test_function_w_keyword_arguments(self):
        first_name = self.first_name
        last_name = random.choice((
            'doe', 'smith', 'blow', 'public',
        ))
        sex = random.choice(('F', 'M'))
        year_of_birth = self.year_of_birth
    
        ...
    
    def test_function_w_default_keyword_arguments(self):
        first_name = self.first_name
        year_of_birth = self.year_of_birth
    
        ...
    

    the terminal still shows green

  • since the variables point to class attributes, I can use them directly

    def test_function_w_keyword_arguments(self):
        first_name = self.first_name
        last_name = random.choice((
            'doe', 'smith', 'blow', 'public',
        ))
        sex = random.choice(('F', 'M'))
        year_of_birth = self.year_of_birth
    
        self.assertEqual(
            src.person.factory(
                first_name=self.first_name,
                last_name=last_name,
                sex=sex,
                year_of_birth=self.year_of_birth
            ),
            dict(
                first_name=self.first_name,
                last_name=last_name,
                sex=sex,
                age=this_year()-self.year_of_birth,
            )
        )
    
    def test_function_w_default_keyword_arguments(self):
        first_name = self.first_name
        year_of_birth = self.year_of_birth
    
        self.assertEqual(
            src.person.factory(
                first_name=self.first_name,
                year_of_birth=self.year_of_birth
            ),
            dict(
                first_name=self.first_name,
                last_name='doe',
                sex='M',
                age=this_year()-self.year_of_birth,
            )
        )
    

    all tests are still passing

  • I remove the variables because they are no longer used

    def test_function_w_keyword_arguments(self):
        last_name = random.choice((
            'doe', 'smith', 'blow', 'public',
        ))
        sex = random.choice(('F', 'M'))
    
        self.assertEqual(
            src.person.factory(
                first_name=self.first_name,
                last_name=last_name,
                sex=sex,
                year_of_birth=self.year_of_birth
            ),
            dict(
                first_name=self.first_name,
                last_name=last_name,
                sex=sex,
                age=this_year()-self.year_of_birth,
            )
        )
    
    def test_function_w_default_keyword_arguments(self):
        self.assertEqual(
            src.person.factory(
                first_name=self.first_name,
                year_of_birth=self.year_of_birth
            ),
            dict(
                first_name=self.first_name,
                last_name='doe',
                sex='M',
                age=this_year()-self.year_of_birth,
            )
        )
    
  • both tests have the same random values for first_name and year_of_birth, they were not always the same before the change. I can use the unittest.TestCase.setUp method which runs before every test to make sure they are assigned to new random values before each test

    class TestPerson(unittest.TestCase):
    
        def setUp(self):
            first_name = random.choice((
                'jane', 'joe', 'john', 'person',
            ))
            year_of_birth = random.randint(
                this_year()-120, this_year()
            )
    
        def test_function_w_keyword_arguments(self):
            ...
    

    the terminal shows AttributeError

    AttributeError: 'TestPerson' object has no attribute 'first_name'
    

    because there is no longer a class attribute with the name, it is local to the unittest.TestCase.setUp method and the other methods cannot reach it

  • I add self. to make it a class attribute

    def setUp(self):
        self.first_name = random.choice((
            'jane', 'joe', 'john', 'person',
        ))
        year_of_birth = random.randint(
            this_year()-120, this_year()
        )
    

    and get another AttributeError

    AttributeError: 'TestPerson' object has no attribute 'year_of_birth'
    
  • same problem, same solution

    def setUp(self):
        self.first_name = random.choice((
            'jane', 'joe', 'john', 'person',
        ))
        self.year_of_birth = random.randint(
            this_year()-120, this_year()
        )
    

    and both tests are green again. self.first_name and self.year_of_birth are given random values before the first test, then given random values again before the second test


test_person_tests

red: make it fail

  • I close test_person.py

  • I want to write the solution without looking at the tests and delete all the text in person.py, the terminal shows AttributeError

    AttributeError: module 'src.person' has no attribute 'factory'
    

green: make it pass

  • I add the name

    factory
    

    the terminal shows NameError

    NameError: name 'factory' is not defined
    
  • I point it to None

    factory = None
    

    which gives me TypeError

    TypeError: 'NoneType' object is not callable
    
  • when I make it a function

    def factory():
        return None
    

    the terminal shows another TypeError

    TypeError: factory() got an unexpected keyword argument 'first_name'
    

    I add the keyword argument to the function definition

    def factory(first_name):
        return None
    

    and get TypeError

    TypeError: factory() got an unexpected keyword argument 'last_name'
    

    I add the keyword argument

    def factory(first_name, last_name):
        return None
    

    and the terminal shows another TypeError

    TypeError: factory() got an unexpected keyword argument 'sex'
    

    when I add the keyword argument

    def factory(first_name, last_name, sex):
        return None
    

    I still get TypeError

    TypeError: factory() got an unexpected keyword argument 'year_of_birth'
    

    I add the missing keyword argument

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return None
    

    and the terminal shows AssertionError

    AssertionError: None != {'first_name': 'john', 'last_name': 'blow', 'sex': 'M', 'age': 20}
    AssertionError: None != {'first_name': 'john', 'last_name': 'smith', 'sex': 'F', 'age': 31}
    AssertionError: None != {'first_name': 'jane', 'last_name': 'blow', 'sex': 'M', 'age': 55}
    AssertionError: None != {'first_name': 'person', 'last_name': 'smith', 'sex': 'F', 'age': 97}
    
  • I copy the value from the terminal to replace None in the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': 'john',
            'last_name': 'blow',
            'sex': 'F',
            'age': 20
        }
    

    which gives me another AssertionError

    AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'smith', 'sex': 'M', 'age': 50}
    AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 73}
    AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'john', 'last_name': 'smith', 'sex': 'M', 'age': 77}
    AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'public', 'sex': 'M', 'age': 98}
    

    the values for first_name, last_name, sex and age change

  • I make the dictionary in the return statement use the first_name input parameter instead of a hardcoded value

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': 'blow',
            'sex': 'F',
            'age': 20
        }
    

    and still have AssertionError

    AssertionError: {'first_name': 'person', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'person', 'last_name': 'public', 'sex': 'M', 'age': 69}
    AssertionError: {'first_name': 'joe', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'joe', 'last_name': 'blow', 'sex': 'M', 'age': 97}
    AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'john', 'last_name': 'smith', 'sex': 'M', 'age': 74}
    AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'john', 'last_name': 'public', 'sex': 'M', 'age': 19}
    

    the last_name, sex and age change

  • I use the last_name input parameter in the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': last_name,
            'sex': 'F',
            'age': 20
        }
    

    and get another AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'blow', 'sex': 'M', 'age': 3}
    AssertionError: {'first_name': 'joe', 'last_name': 'smith', 'sex': 'F', 'age': 20} != {'first_name': 'joe', 'last_name': 'smith', 'sex': 'M', 'age': 118}
    AssertionError: {'first_name': 'joe', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'joe', 'last_name': 'blow', 'sex': 'M', 'age': 19}
    AssertionError: {'first_name': 'joe', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'joe', 'last_name': 'blow', 'sex': 'M', 'age': 95}
    

    the values for sex and age change

  • When I add the sex input parameter to the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': last_name,
            'sex': sex,
            'age': 20
        }
    

    the terminal shows another AssertionError

    AssertionError: {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 20} != {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 1}
    AssertionError: {'first_name': 'joe', 'last_name': 'public', 'sex': 'F', 'age': 20} != {'first_name': 'joe', 'last_name': 'public', 'sex': 'F', 'age': 90}
    AssertionError: {'first_name': 'joe', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'joe', 'last_name': 'blow', 'sex': 'F', 'age': 113}
    AssertionError: {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 20} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 58}
    

    the age is different

  • I add the year_of_birth input parameter to the return statement

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': last_name,
            'sex': sex,
            'age': year_of_birth,
        }
    

    and get AssertionError

    AssertionError: {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 2022} != {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 2}
    AssertionError: {'first_name': 'jane', 'last_name': 'smith', 'sex': 'M', 'age': 2024} != {'first_name': 'jane', 'last_name': 'smith', 'sex': 'M', 'age': 0}
    AssertionError: {'first_name': 'jane', 'last_name': 'blow', 'sex': 'F', 'age': 1981} != {'first_name': 'jane', 'last_name': 'blow', 'sex': 'F', 'age': 43}
    AssertionError: {'first_name': 'person', 'last_name': 'smith', 'sex': 'M', 'age': 1969} != {'first_name': 'person', 'last_name': 'smith', 'sex': 'M', 'age': 55}
    

    I need the difference between the current year and the year_of_birth to get the age

  • I add an import statement

    import datetime
    
    
    def factory(
    ...
    

    then use it to get the current year for the age calculation

    def factory(
            first_name, last_name,
            sex, year_of_birth
        ):
        return {
            'first_name': first_name,
            'last_name': last_name,
            'sex': sex,
            'age': datetime.datetime.today().year - year_of_birth,
        }
    

    and the terminal shows TypeError

    TypeError: factory() missing 2 required positional arguments: 'last_name' and 'sex'
    
  • I add a default value for last_name

    def factory(
            first_name, last_name=None,
            sex, year_of_birth
        ):
        ...
    

    and get a SyntaxError

  • When I add a default value for sex

    def factory(
            first_name, last_name=None,
            sex=None, year_of_birth
        ):
        ...
    

    the terminal shows the same SyntaxError

    SyntaxError: parameter without a default follows parameter with a default
    
  • I add a default value for year_of_birth

    def factory(
            first_name, last_name=None,
            sex=None, year_of_birth=None
        ):
    

    and the terminal shows AssertionError

    AssertionError: {'first_name': 'person', 'last_name': None, 'sex': None, 'age': 76} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 76}
    AssertionError: {'first_name': 'john', 'last_name': None, 'sex': None, 'age': 101} != {'first_name': 'john', 'last_name': 'doe', 'sex': 'M', 'age': 101}
    AssertionError: {'first_name': 'joe', 'last_name': None, 'sex': None, 'age': 23} != {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 23}
    AssertionError: {'first_name': 'person', 'last_name': None, 'sex': None, 'age': 29} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 29}
    

    the values for last_name and sex do not match the expectation

  • I change the default value for last_name

    def factory(
            first_name, last_name='doe',
            sex=None, year_of_birth=None
        ):
        ...
    

    and get another AssertionError

    AssertionError: {'first_name': 'john', 'last_name': 'doe', 'sex': None, 'age': 51} != {'first_name': 'john', 'last_name': 'doe', 'sex': 'M', 'age': 51}
    AssertionError: {'first_name': 'joe', 'last_name': 'doe', 'sex': None, 'age': 18} != {'first_name': 'joe', 'last_name': 'doe', 'sex': 'M', 'age': 18}
    AssertionError: {'first_name': 'person', 'last_name': 'doe', 'sex': None, 'age': 3} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 3}
    AssertionError: {'first_name': 'person', 'last_name': 'doe', 'sex': None, 'age': 67} != {'first_name': 'person', 'last_name': 'doe', 'sex': 'M', 'age': 67}
    
  • when I make the default value for sex match the expectation

    def factory(
            first_name, last_name='doe',
            sex='M', year_of_birth=None
        ):
        ...
    

    both tests pass!

review

I ran tests to make a function that takes in keyword arguments as input, has default values for some of them, performs an action based on an input and returns a dictionary as output

I also ran into the following Exceptions

Would you like to know how to test that an Exception is raised?


how to make a person: tests and solution