how to measure sleep duration: test_duration_tests


This is part 5 of a program that calculates the difference between a given wake and sleep time.


I want to write a program that makes the tests in test_sleep_duration.py pass without looking at them

red: make it fail

  • I close test_sleep_duration.py

  • then delete all the text in sleep_duration.py and the terminal shows AttributeError

    AttributeError: module 'sleep_duration' has no attribute 'get_datetime'
    

green: make it pass

  • I add a list of Exceptions encountered

    # Exceptions Encountered
    # AttributeError
    
  • and the missing name to sleep_duration.py

    get_datetime
    

    the terminal shows NameError

    NameError: name 'get_datetime' is not defined
    

    I add it to the list of Exceptions encountered as well

    # Exceptions Encountered
    # AttributeError
    # NameError
    
  • and point get_datetime to None

    get_datetime = None
    

    which gives me TypeError

    TypeError: 'NoneType' object is not callable
    

    another error for the list of Exceptions encountered

    # Exceptions Encountered
    # AttributeError
    # NameError
    # TypeError
    
  • I change get_datetime to a function to make it callable

    def get_datetime():
        return None
    

    and the terminal shows another TypeError

    TypeError: get_datetime() takes 0 positional arguments but 1 was given
    

    then I add a name to the function’s signature

    def get_datetime(argument):
    

    and get AssertionError

    None != datetime.datetime(2006, 11, 21, 7, 1)
    None != datetime.datetime(2006, 11, 21, 10, 59)
    None != datetime.datetime(2006, 11, 21, 13, 28)
    None != datetime.datetime(2006, 11, 21, 19, 8)
    

    which I add to the list of Exceptions encountered

    # Exceptions Encountered
    # AttributeError
    # NameError
    # TypeError
    # AssertionError
    
  • I copy the value from the terminal to change None in the return statement

    def get_datetime(argument):
        return datetime.datetime(2006, 11, 21, 19, 8)
    

    and the terminal shows NameError

    NameError: name 'datetime' is not defined. Did you forget to import 'datetime'
    
  • I add an import statement for the datetime module

    import datetime
    
    
    def get_datetime(argument):
    ...
    

    and get AssertionError

    AssertionError: datetime.datetime(2006, 11, 21, 19, 8) != datetime.datetime(2006, 11, 21, 0, 15)
    

    the expected values of the test changed

  • I change the return statement to see the difference between the input and expected output

    def get_datetime(argument):
        return argument
    

    and the terminal shows AssertionError

    AssertionError: '2006/11/21 02:58' != datetime.datetime(2006, 11, 21, 2, 58)
    AssertionError: '2006/11/21 03:14' != datetime.datetime(2006, 11, 21, 3, 14)
    AssertionError: '2006/11/21 08:30' != datetime.datetime(2006, 11, 21, 8, 30)
    AssertionError: '2006/11/21 23:41' != datetime.datetime(2006, 11, 21, 23, 41)
    

    I need a way to change a string that has a date and time to a datetime.datetime object

  • I use the datetime.datetime.strptime method to make it happen

    def get_datetime(argument):
        return datetime.datetime.strptime(
            argument, '%Y/%m/%d %H:%M'
        )
    

    and get AttributeError

    AttributeError: module 'sleep_duration' has no attribute 'duration'
    
  • I add the name below get_datetime

    duration
    

    and get NameError

    NameError: name 'duration' is not defined
    

    I have done this dance before

  • I point it to None to define it

    duration = None
    

    which gives me TypeError

    TypeError: 'NoneType' object is not callable
    
  • then change it to a function to make it callable

    def duration():
        return None
    

    and the terminal shows another TypeError

    TypeError: duration() got an unexpected keyword argument 'sleep_time'
    

    I add the name to the function’s signature

    def duration(sleep_time):
    

    and get TypeError for another keyword argument

    TypeError: duration() got an unexpected keyword argument 'wake_time'
    

    which I add to the signature

    def duration(sleep_time, wake_time):
    

    the terminal shows this AssertionError

    AssertionError: ValueError not raised
    

    or

    AssertionError: None != '2944049 days, 15:58:00'
    AssertionError: None != '130331 days, 11:57:00'
    AssertionError: None != '1829637 days, 5:10:00'
    AssertionError: None != '2846203 days, 15:15:00'
    

    it looks like the duration function has to make a decision based on its inputs

  • I change the return statement to raise ValueError with the inputs or return the inputs to see the difference between them and the expected output

    def duration(sleep_time, wake_time):
        raise ValueError(sleep_time, wake_time)
    
  • When I raise ValueError in duration I get another AssertionError because the message in the ValueError does not match the expectation of the test

    AssertionError: "wake_time: "3133/04/18 05:11" is earlier than sleep_time: "6999/09/22 05:07"" does not match "('6999/09/22 05:07', '3133/04/18 05:11')"
    AssertionError: "wake_time: "5856/04/20 15:58" is earlier than sleep_time: "7186/01/12 06:39"" does not match "('7186/01/12 06:39', '5856/04/20 15:58')"
    AssertionError: "wake_time: "5602/08/29 05:06" is earlier than sleep_time: "8373/05/08 05:29"" does not match "('8373/05/08 05:29', '5602/08/29 05:06')"
    AssertionError: "wake_time: "7413/05/24 15:04" is earlier than sleep_time: "8720/08/18 01:02"" does not match ""
    

    this tells me that the test expects a message with the ValueError, or I get ValueError that looks like this

    ValueError: ('0887/08/27 17:21', '5668/08/15 20:16')
    ValueError: ('2880/08/20 10:10', '9134/08/22 20:28')
    ValueError: ('6471/03/10 05:04', '7883/06/01 02:38')
    ValueError: ('7370/08/12 21:34', '7937/03/27 01:58')
    

    which does not tell me anything so I comment it out to get the other message I got with the AssertionError, I can raise ValueError again or try to return the inputs

  • When I get the error with the message about wake_time being earlier than sleep_time, I copy it from the terminal to change the message of the ValueError

    def duration(wake_time, sleep_time):
        raise ValueError(
            "wake_time: "7413/05/24 15:04" is earlier than sleep_time: "8720/08/18 01:02"" does not match ""
        )
    

    and get a SyntaxError with this message

    SyntaxError: invalid syntax. Perhaps you forgot a comma?
    

    or this message

    SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
    

    Python does not know where the string ends or begins because the message has double quotes inside double quotes. I add the error to the list of Exceptions

    # Exceptions Encountered
    # AttributeError
    # NameError
    # TypeError
    # SyntaxError
    

    then change the outer double quotes to single quotes

    def duration(wake_time, sleep_time):
        raise ValueError(
            'wake_time: "7413/05/24 15:04"'
            ' is earlier than '
            'sleep_time: "8720/08/18 01:02"'
        )
    

    and get another AssertionError because the timestamps in the ValueError message are not the same

    AssertionError: "wake_time: "1184/07/11 12:07" is earlier than sleep_time: "3059/12/16 04:30"" does not match "wake_time: "0615/04/17 08:51" is earlier than sleep_time: "6631/03/18 20:25""
    AssertionError: "wake_time: "2476/05/07 19:46" is earlier than sleep_time: "9204/03/10 20:53"" does not match "wake_time: "0615/04/17 08:51" is earlier than sleep_time: "6631/03/18 20:25""
    AssertionError: "wake_time: "3208/04/09 09:10" is earlier than sleep_time: "3957/12/23 22:44"" does not match "wake_time: "0615/04/17 08:51" is earlier than sleep_time: "6631/03/18 20:25""
    AssertionError: "wake_time: "7169/09/04 15:18" is earlier than sleep_time: "9367/03/02 03:17"" does not match "wake_time: "0615/04/17 08:51" is earlier than sleep_time: "6631/03/18 20:25""
    

    or ValueError

    ValueError: wake_time: "7413/05/24 15:04" is earlier than sleep_time: "8720/08/18 01:02"
    

    which tells me nothing, so I return sleep_time and wake_time

    def duration(sleep_time, wake_time):
        return (sleep_time, wake_time)
        # raise ValueError(
        #    'wake_time: "7413/05/24 15:04"'
        #    ' is earlier than '
        #    'sleep_time: "8720/08/18 01:02"'
        # )
    

    and get the AssertionError I got before. I keep switching between the return statement and raise ValueError until I get the AssertionError that the ValueError messages do not match

  • I interpolate wake_time and sleep_time in the message

    def duration(wake_time, sleep_time):
        # return (sleep_time, wake_time)
        raise ValueError(
            f'wake_time: "{wake_time}"'
            ' is earlier than '
            f'sleep_time: "{sleep_time}"'
        )
    

    and get ValueError

    ValueError: wake_time: "9251/06/04 01:20" is earlier than sleep_time: "1034/03/24 22:35"
    ValueError: wake_time: "2669/08/09 17:30" is earlier than sleep_time: "2520/01/27 06:40"
    ValueError: wake_time: "3201/08/13 15:20" is earlier than sleep_time: "1074/03/31 16:44"
    ValueError: wake_time: "9810/07/30 04:29" is earlier than sleep_time: "9792/03/04 12:44"
    

    this is not right, the timestamps for wake_time are not earlier than sleep_time. The duration function needs a condition to make sure it raises ValueError only when wake_time is earlier than sleep_time. I add the error to the list of Exceptions encountered

    # Exceptions Encountered
    # AttributeError
    # NameError
    # TypeError
    # SyntaxError
    # ValueError
    

    then add a condition based on the message from the ValueError

    def duration(sleep_time, wake_time):
        if wake_time < sleep_time:
            raise ValueError(
                f'wake_time: "{wake_time}"'
                ' is earlier than '
                f'sleep_time: "{sleep_time}"'
            )
    else:
        return (sleep_time, wake_time)
    

    and the terminal shows AssertionError

    AssertionError: ('0435/03/20 02:03', '0711/05/03 10:35') != '100850 days, 8:32:00'
    AssertionError: ('2544/12/29 13:05', '4351/03/05 09:47') != '659692 days, 20:42:00'
    AssertionError: ('1583/06/02 07:48', '3962/03/06 17:07') != '868824 days, 9:19:00'
    AssertionError: ('1820/06/12 16:07', '8786/05/18 04:27') != '2544253 days, 12:20:00'
    

    it looks like the test expects the difference between the timestamps

  • I return the difference between wake_time and sleep_time

    def duration(sleep_time, wake_time):
        if wake_time < sleep_time:
            raise ValueError(
                f'wake_time: "{wake_time}"'
                ' is earlier than '
                f'sleep_time: "{sleep_time}"'
            )
        else:
            return (sleep_time - wake_time)
    

    and get TypeError

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

    I still cannot subtract one string from another

  • I change the return statement back, then add calls to get_datetime because I can do arithmetic with datetime.datetime objects

    def duration(sleep_time, wake_time):
        if wake_time < sleep_time:
            raise ValueError(
                f'wake_time: "{wake_time}"'
                ' is earlier than '
                f'sleep_time: "{sleep_time}"'
            )
        else:
            return (
                get_datetime(sleep_time)
              - get_datetime(wake_time)
            )
    

    and the terminal shows AssertionError

    AssertionError: datetime.timedelta(days=-43256, seconds=82860) != '43255 days, 0:59:00'
    AssertionError: datetime.timedelta(days=-28643, seconds=68100) != '28642 days, 5:05:00'
    AssertionError: datetime.timedelta(days=-744003, seconds=22500) != '744002 days, 17:45:00'
    AssertionError: datetime.timedelta(days=-1226280, seconds=76800) != '1226279 days, 2:40:00'
    

    the test expects a string and the function returns a datetime.timedelta object. The values for days are also negative and the test expects positive numbers for the days, I did something wrong

  • I add the str constructor to match the format of the expectation

    def duration(sleep_time, wake_time):
        if wake_time < sleep_time:
            raise ValueError(
                f'wake_time: "{wake_time}"'
                ' is earlier than '
                f'sleep_time: "{sleep_time}"'
            )
        else:
            return str(
                get_datetime(sleep_time)
              - get_datetime(wake_time)
            )
    

    and get another AssertionError

    AssertionError: '-2681410 days, 19:19:00' != '2681409 days, 4:41:00'
    AssertionError: '-1492190 days, 13:23:00' != '1492189 days, 10:37:00'
    AssertionError: '-398812 days, 16:44:00' != '398811 days, 7:16:00'
    AssertionError: '-1209690 days, 0:49:00' != '1209689 days, 23:11:00'
    

    the duration function returns negative timestamps but the test expects positive timestamps, and the negative days all look like they are one number less than the expectation

  • I switch wake_time and sleep_time in the return statement

    def duration(sleep_time, wake_time):
        if wake_time < sleep_time:
            raise ValueError(
                f'wake_time: "{wake_time}"'
                ' is earlier than '
                f'sleep_time: "{sleep_time}"'
            )
        else:
            return str(
                get_datetime(wake_time)
              - get_datetime(sleep_time)
            )
    

    and the terminal shows passing tests! YES!!

refactor: make it better

  • I change the name of the positional argument in get_datetime to be more descriptive

    def get_datetime(timestamp):
        return datetime.datetime.strptime(
            timestamp, '%Y/%m/%d %H:%M'
        )
    

    and the terminal shows all tests are still passing

  • then I remove the list of Exceptions encountered because it was just for me

review

The challenge was to write a program that makes the tests in test_sleep_duration.py pass without looking at them. I wrote something that returns the difference between a given wake_time and sleep_time by following these Exceptions from the terminal


how to measure sleep duration: tests and solution