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.pythen delete all the text in
sleep_duration.pythe terminal shows AttributeErrorAttributeError: module 'src.sleep_duration' has no attribute 'get_datetime'
GREEN: make it pass¶
I add a list of Exceptions encountered in
test_sleep_duration.py# Exceptions Encountered # AttributeError
and the missing name to
sleep_duration.pyget_datetimeNameError: name 'get_datetime' is not defined
I add it to the list of Exceptions encountered as well
# Exceptions Encountered # AttributeError # NameError
and point
get_datetimeto Noneget_datetime = None
TypeError: 'NoneType' object is not callable
another error for the list of Exceptions encountered in
test_sleep_duration.py# Exceptions Encountered # AttributeError # NameError # TypeError
I change
get_datetimeto a function to make it callabledef get_datetime(): return None
TypeError: get_datetime() takes 0 positional arguments but 1 was given
then I add a name to the function’s definition
def get_datetime(the_input):
the terminal shows 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 in
test_sleep_duration.py# Exceptions Encountered # AttributeError # NameError # TypeError # AssertionError
I copy the value from the terminal to change None in the return statement
def get_datetime(the_input): return datetime.datetime(2006, 11, 21, 19, 8)
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(the_input): ...
the terminal shows 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(the_input): return the_input
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(the_input): return datetime.datetime.strptime( argument, '%Y/%m/%d %H:%M' )
the terminal shows AttributeError
AttributeError: module 'src.sleep_duration' has no attribute 'duration'
I add the name below
get_datetimedurationNameError: name 'duration' is not defined
I have done this dance before
I point it to None to define it
duration = None
TypeError: 'NoneType' object is not callable
then change it to a function to make it callable
def duration(): return None
TypeError: duration() got an unexpected keyword argument 'sleep_time'
I add the name to the function’s definition
def duration(sleep_time):
TypeError: duration() got an unexpected keyword argument 'wake_time'
which I add to the definition
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
durationfunction has to make a decision based on its inputsI 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
durationthe terminal shows AssertionError because the message in the ValueError does not match the expectation of the testAssertionError: "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_timebeing earlier thansleep_time, I copy it from the terminal to change the message of the ValueErrordef 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 "" )
the terminal shows SyntaxError
SyntaxError: invalid syntax. Perhaps you forgot a comma?
or this message
SyntaxError: leading zeros in decimal integer literals are not first_inputermitted; 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"' )
the terminal shows 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_timeandwake_timedef 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"' # )
the terminal shows the AssertionError I got before. I keep switching between the return statement and
raise ValueErroruntil I get the AssertionError that the ValueError messages do not matchI interpolate
wake_timeandsleep_timein the messagedef 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}"' )
the terminal shows 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_timeare not earlier thansleep_time. Thedurationfunction needs a condition to make sure it raises ValueError only whenwake_timeis earlier thansleep_time. I add the error to the list of Exceptions encountered intest_sleep_duration.py# 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)
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_timeandsleep_timedef 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)
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_datetimebecause I can do arithmetic with datetime.datetime objectsdef 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) )
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) )
the terminal shows 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
durationfunction returns negative timestamps but the test expects positive timestamps, and the negative days all look like they are one number less than the expectationI switch
wake_timeandsleep_timein the return statementdef 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) )
the test passes. YES!!
REFACTOR: make it better¶
I change the name of the positional argument in
get_datetimeto be more descriptivedef get_datetime(timestamp): return datetime.datetime.strptime( timestamp, '%Y/%m/%d %H:%M' )
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