how to measure sleep duration: test_duration_w_date_and_time¶
This is part 4 of a program that calculates the difference between a given wake and sleep time.
I want to test the duration function with timestamps that have dates
red: make it fail¶
I make a copy of
random_timestamp, change the name of the copy, then add a date to the return statementdef random_timestamp_a(): return ( '1999/12/31 ' f'{random.randint(0,23):02}:' f'{random.randint(0,59):02}' )
I also make a copy of
test_duration_w_hours_and_minutesand change the name totest_duration_w_date_and_timethen add calls to
random_timestamp_adef test_duration_w_date_and_time(self): sleep_time = random_timestamp_a() wake_time = random_timestamp_a() while wake_time < sleep_time: self.assertWakeTimeEarlier( wake_time, sleep_time ) wake_time = random_timestamp_a() else: self.assertEqual( src.sleep_duration.duration( sleep_time=sleep_time, wake_time=wake_time ), self.get_difference( wake_time=wake_time, sleep_time=sleep_time ) )
the terminal shows ValueError
ValueError: invalid literal for int() with base 10: '1999/12/31 03' ValueError: invalid literal for int() with base 10: '1999/12/31 07' ValueError: invalid literal for int() with base 10: '1999/12/31 11' ValueError: invalid literal for int() with base 10: '1999/12/31 21'
the test calls
duration, which callsread_timestamp, which uses the int constructor to change the timestamp string to a number after it calls str.split, but it is not in the right format
green: make it pass¶
I add the unittest.skip decorator to
test_duration_w_date_and_time@unittest.skip def test_duration_w_date_and_time(self): ...
then add an assertion in
test_converting_strings_to_numbersto see if I can make the same ValueError happen again to make sure the problem is with converting the string to a numberdef test_converting_strings_to_numbers(self): self.assertEqual(int('12'), 12) self.assertEqual(int('01'), 1) int('1999/12/31 21')
the terminal shows ValueError with the same message from
test_duration_w_date_and_timeValueError: invalid literal for int() with base 10: '1999/12/31 21'
I cannot use the int constructor to change a timestamp string to a number when it has a date. I add an assertRaises to handle the ValueError
def test_converting_strings_to_numbers(self): self.assertEqual(int('12'), 12) self.assertEqual(int('01'), 1) with self.assertRaises(ValueError): int('1999/12/31 21')
and the test is green again
I remove the unittest.skip decorator from
test_duration_w_date_and_timethen change the call in the assertion to a different function
def test_duration_w_date_and_time(self): sleep_time = random_timestamp_a() wake_time = random_timestamp_a() while wake_time < sleep_time: self.assertWakeTimeEarlier( wake_time=wake_time, sleep_time=sleep_time ) wake_time = random_timestamp_a() else: self.assertEqual( src.sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), self.get_difference( wake_time=wake_time, sleep_time=sleep_time ) )
the terminal shows AttributeError
AttributeError: module 'src.sleep_duration' has no attribute 'duration_a'...
I make a copy of the
durationfunction insleep_duration.pyand change the name toduration_ato keep the working solution while I try a new one. I change theelseblock to return Nonedef duration_a(wake_time=None, sleep_time=None): if wake_time < sleep_time: raise ValueError( f'wake_time: "{wake_time}"' ' is earlier than ' f'sleep_time: "{sleep_time}"' ) else: return None
the terminal shows ValueError
ValueError: invalid literal for int() with base 10: '1999/12/31 22'
because the test calls the
get_differencemethod in the expectation which uses the int constructorI change it to return
wake_timeandsleep_timedef test_duration_w_date_and_time(self): sleep_time = random_timestamp_a() wake_time = random_timestamp_a() while wake_time < sleep_time: self.assertWakeTimeEarlier( sleep_time=sleep_time, wake_time=wake_time, ) wake_time = random_timestamp_a() else: self.assertEqual( src.sleep_duration.duration_a( wake_time=wake_time, sleep_time=sleep_time ), (wake_time, sleep_time) )
the terminal shows AssertionError
AssertionError: None != ('1999/12/31 09:52', '1999/12/31 07:11') AssertionError: None != ('1999/12/31 18:16', '1999/12/31 11:21') AssertionError: None != ('1999/12/31 13:10', '1999/12/31 12:00') AssertionError: None != ('1999/12/31 16:41', '1999/12/31 12:35')
I change the return statement in
duration_adef duration_a(wake_time=None, sleep_time=None): if wake_time < sleep_time: raise ValueError( f'wake_time: "{wake_time}"' ' is earlier than ' f'sleep_time: "{sleep_time}"' ) else: return (wake_time, sleep_time)
the test passes
refactor: make it better¶
I want something that can read dates and times from timestamps. I search python’s online documentation for date and time to see if there is an existing solution and select the datetime module from the results. The available types in the module show datetime.datetime objects
class datetime.datetime
A combination of a date and a time.
Attributes: year, month, day, hour,
minute, second, microsecond, and tzinfo.
test_datetime_objects¶
red: make it fail¶
I add a test to test_sleep_duration.py from Examples of usage: datetime for datetime.datetime objects
def test_datetime_objects(self):
self.assertEqual(
datetime.strptime(
"21/11/06 16:30",
"%d/%m/%y %H:%M"
),
''
)
def test_duration_w_date_and_time(self):
...
the terminal shows NameError
NameError: name 'datetime' is not defined. Did you forget to import 'datetime'
green: make it pass¶
I add an import statement for the datetime module
import datetime
import random
import src.sleep_duration
import unittest
...
the terminal shows AttributeError
AttributeError: module 'datetime' has no attribute 'strptime'
because my import statement is different from the example in the documentation, so I add the module name to the call
def test_datetime_objects(self):
self.assertEqual(
datetime.datetime.strptime(
"21/11/06 16:30",
"%d/%m/%y %H:%M"
),
''
)
the terminal shows AssertionError
AssertionError: datetime.datetime(2006, 11, 21, 16, 30) != ''
I copy the value from the left side of the AssertionError and paste it as the expected value in the test
def test_datetime_objects(self):
self.assertEqual(
datetime.datetime.strptime(
"21/11/06 16:30",
"%d/%m/%y %H:%M"
),
datetime.datetime(
2006, 11, 21, 16, 30
)
)
and it passes
The datetime.datetime.strptime method returns a datetime.datetime object when given 2 strings as inputs - a timestamp and a pattern that is for the timestamp. The pattern provided is
%dfor days%mfor months%yfor 2 digit years%Hfor hours%Mfor minutes
there are more details in strftime() and strptime() behavior
refactor: make it better¶
I change the order in the date to test the pattern
def test_datetime_objects(self): self.assertEqual( datetime.datetime.strptime( "06/11/21 16:30", "%d/%m/%y %H:%M" ), datetime.datetime( 2006, 11, 21, 16, 30 ) )
the terminal shows AssertionError
AssertionError: datetime.datetime(2021, 11, 6, 16, 30) != datetime.datetime(2006, 11, 21, 16, 30)
when I change the pattern to match
def test_datetime_objects(self): self.assertEqual( datetime.datetime.strptime( "06/11/21 16:30", "%y/%m/%d %H:%M" ), datetime.datetime( 2006, 11, 21, 16, 30 ) )
the test passes
I change the year to four digits
def test_datetime_objects(self): self.assertEqual( datetime.datetime.strptime( "2006/11/21 16:30", "%y/%m/%d %H:%M" ), datetime.datetime( 2006, 11, 21, 16, 30 ) )
the terminal shows ValueError
ValueError: time data '2006/11/21 16:30' does not match format '%y/%m/%d %H:%M'
and when I change the pattern to use
%Yfor the yeardef test_datetime_objects(self): self.assertEqual( datetime.datetime.strptime( "2006/11/21 16:30", "%Y/%m/%d %H:%M" ), datetime.datetime( 2006, 11, 21, 16, 30 ) )
the terminal shows green again
I add calls to the datetime.datetime.strptime method in
test_duration_w_date_and_timedef test_duration_w_date_and_time(self): sleep_time = random_timestamp_a() wake_time = random_timestamp_a() while wake_time < sleep_time: self.assertWakeTimeEarlier( wake_time=wake_time, sleep_time=sleep_time ) wake_time = random_timestamp_a() else: self.assertEqual( src.sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), ( datetime.datetime.strptime( wake_time, '%Y/%m/%d %H:%M' ), datetime.datetime.strptime( sleep_time, '%Y/%m/%d %H:%M' ) ) )
the terminal shows AssertionError
AssertionError: Tuples differ: ('1999/12/31 07:20', '1999/12/31 03:08') != (datetime.datetime(1999, 12, 31, 7, 20), datetime.datetime(1999, 12, 31, 3, 8)) AssertionError: Tuples differ: ('1999/12/31 15:01', '1999/12/31 00:37') != (datetime.datetime(1999, 12, 31, 15, 1), datetime.datetime(1999, 12, 31, 0, 37)) AssertionError: Tuples differ: ('1999/12/31 20:50', '1999/12/31 14:22') != (datetime.datetime(1999, 12, 31, 20, 50), [35 chars] 22)) AssertionError: Tuples differ: ('1999/12/31 16:40', '1999/12/31 13:39') != (datetime.datetime(1999, 12, 31, 16, 40), [35 chars] 39))
duration_areturns the timestamps as strings and the test expects them as datetime.datetime objects. I change the return statement to matchdef duration_a(wake_time=None, sleep_time=None): if wake_time < sleep_time: raise ValueError( f'wake_time: "{wake_time}"' ' is earlier than ' f'sleep_time: "{sleep_time}"' ) else: return ( datetime.datetime.strptime( wake_time, '%Y/%m/%d %H:%M' ), datetime.datetime.strptime( sleep_time, '%Y/%m/%d %H:%M' ) )
the terminal shows NameError
NameError: name 'datetime' is not defined. Did you forget to import 'datetime'
I add an import statement to the top of
sleep_duration.pyimport datetime def read_timestamp(timestamp=None, index=0): ...
the test passes
I just called datetime.datetime.strptime 5 times in a row with the same pattern, time to add a function to remove some repetition
def get_datetime(timestamp): return datetime.datetime.strptime( timestamp, '%Y/%m/%d %H:%M' ) def duration_a(wake_time=None, sleep_time=None): ...
and call it in the return statement of
duration_adef duration_a(wake_time=None, sleep_time=None): if wake_time < sleep_time: raise ValueError( f'wake_time: "{wake_time}"' ' is earlier than ' f'sleep_time: "{sleep_time}"' ) else: return ( get_datetime(wake_time), get_datetime(sleep_time) )
still green!
test_get_datetime¶
red: make it fail¶
I want a test for the get_datetime function so I change the name of test_datetime_objects to test_get_datetime and make it call src.sleep_duration.get_datetime which calls the datetime.datetime.strptime method
def test_get_datetime(self):
self.assertEqual(
src.sleep_duration.get_datetime(
"21/11/06 16:30"
),
datetime.datetime(
2006, 11, 21, 16, 30
)
)
I change the expectation to use datetime.datetime.strptime
def test_get_datetime(self):
self.assertEqual(
src.sleep_duration.get_datetime(
"2006/11/21 16:30"
),
datetime.datetime.strptime(
'2006/11/21 16:30',
'%Y/%m/%d %H:%M'
)
)
then add a variable for a random timestamp
def test_get_datetime(self):
timestamp = random_timestamp_a()
self.assertEqual(
src.sleep_duration.get_datetime(
timestamp
),
datetime.datetime.strptime(
'2006/11/21 16:30',
'%Y/%m/%d %H:%M'
)
)
the terminal shows AssertionError
AssertionError: datetime.datetime(1999, 12, 31, 0, 23) != datetime.datetime(2006, 11, 21, 16, 30)
AssertionError: datetime.datetime(1999, 12, 31, 8, 55) != datetime.datetime(2006, 11, 21, 16, 30)
AssertionError: datetime.datetime(1999, 12, 31, 9, 16) != datetime.datetime(2006, 11, 21, 16, 30)
AssertionError: datetime.datetime(1999, 12, 31, 15, 5) != datetime.datetime(2006, 11, 21, 16, 30)
green: make it pass¶
I add the variable to the expectation
def test_get_datetime(self):
timestamp = random_timestamp_a()
self.assertEqual(
src.sleep_duration.get_datetime(
timestamp
),
datetime.datetime.strptime(
timestamp,
'%Y/%m/%d %H:%M'
)
)
the terminal shows green again
refactor: make it better¶
I change the calls to datetime.datetime.strptime to src.sleep_duration.get_datetime
def test_duration_w_date_and_time(self):
sleep_time = random_timestamp_a()
wake_time = random_timestamp_a()
while wake_time < sleep_time:
self.assertWakeTimeEarlier(
wake_time=wake_time,
sleep_time=sleep_time
)
wake_time = random_timestamp_a()
else:
self.assertEqual(
src.sleep_duration.duration_a(
sleep_time=sleep_time,
wake_time=wake_time
),
(
src.sleep_duration.get_datetime(
wake_time
),
src.sleep_duration.get_datetime(
sleep_time
)
)
)
and the test is still green
I change the expectation in the test to the difference between the timestamps because I can do arithmetic with datetime.datetime objects
def test_duration_w_date_and_time(self): sleep_time = random_timestamp_a() wake_time = random_timestamp_a() while wake_time < sleep_time: self.assertWakeTimeEarlier( wake_time=wake_time, sleep_time=sleep_time ) wake_time = random_timestamp_a() else: self.assertEqual( src.sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), ( src.sleep_duration.get_datetime( wake_time ) - src.sleep_duration.get_datetime( sleep_time ) ) )
the terminal shows AssertionError
AssertionError: (datetime.datetime(1999, 12, 31, 16, 1), datetime.datetime(1999, 12, 31, 14, 6)) != datetime.timedelta(seconds=6900) AssertionError: (datetime.datetime(1999, 12, 31, 9, 57), datetime.datetime(1999, 12, 31, 1, 3)) != datetime.timedelta(seconds=32040) AssertionError: (datetime.datetime(1999, 12, 31, 23, 59),[35 chars], 1)) != datetime.timedelta(seconds=7080) AssertionError: (datetime.datetime(1999, 12, 31, 16, 1), [35 chars] 55)) != datetime.timedelta(seconds=7560)
the
duration_afunction returns datetime.datetime objects and the test expects a datetime.timedelta object. I change it to match the expectationdef duration_a(wake_time=None, sleep_time=None): if wake_time < sleep_time: raise ValueError( f'wake_time: "{wake_time}"' ' is earlier than ' f'sleep_time: "{sleep_time}"' ) else: return ( get_datetime(wake_time) - get_datetime(sleep_time) )
the test passes
I add the str constructor to the expectation in the test because I want the result as a string not a datetime.timedelta object
def test_duration_w_date_and_time(self): sleep_time = random_timestamp_a() wake_time = random_timestamp_a() while wake_time < sleep_time: self.assertWakeTimeEarlier( wake_time=wake_time, sleep_time=sleep_time ) wake_time = random_timestamp_a() else: self.assertEqual( src.sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), str( src.sleep_duration.get_datetime( wake_time ) - src.sleep_duration.get_datetime( sleep_time ) ) )
the terminal shows AssertionError
AssertionError: datetime.timedelta(seconds=4440) != '1:14:00' AssertionError: datetime.timedelta(seconds=15780) != '4:23:00' AssertionError: datetime.timedelta(seconds=31020) != '8:37:00' AssertionError: datetime.timedelta(seconds=49920) != '13:52:00'
when I make the same change to the return statement in
duration_adef duration_a(wake_time=None, sleep_time=None): 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
I remove
durationbecauseduration_ais a better solutionwhich means I can remove
read_timestampbecause no one calls it anymore. The terminal shows AttributeErrorAttributeError: module 'src.sleep_duration' has no attribute 'duration'...
I change the name of
duration_atodurationinsleep_duration.pyandtest_sleep_duration.pywhich leaves me with ValueErrorValueError: time data '04:51' does not match format '%Y/%m/%d %H:%M' ValueError: time data '13:35' does not match format '%Y/%m/%d %H:%M' ValueError: time data '12:26' does not match format '%Y/%m/%d %H:%M' ValueError: time data '23:20' does not match format '%Y/%m/%d %H:%M'
test_duration_w_hours_and_minutesdoes not have dates in its timestamps. I remove it because it is covered bytest_duration_w_date_and_timethen remove
get_differencebecause no one calls it anymoreI also remove the following tests because they do not test the solution directly
test_the_modulo_operationtest_floor_aka_integer_divisiontest_converting_strings_to_numberstest_string_splitting
and remove
random_timestampbecause no one calls it anymorethen change the name of
random_timestamp_atorandom_timestampthe new
random_timestampfunction always returns timestamps with the same date, I change it to return random dates as welldef random_timestamp(date): return ( f'{random.randint(0,9999):04}/' f'{random.randint(1,12):02}/' f'{random.randint(1,31):02} ' f'{random.randint(0,23):02}:' f'{random.randint(0,59):02}' )
the terminal shows a random ValueError
ValueError: day is out of range for month
I need to make sure the random dates that are made by the function are real dates
I change the name of
random_timestamptoget_random_timestampand make a new function to make sure the random timestamps generated are gooddef get_random_timestamp(): return ( f'{random.randint(0,9999):04}/' f'{random.randint(1,12):02}/' f'{random.randint(1,31):02} ' f'{random.randint(0,23):02}:' f'{random.randint(0,59):02}' ) def random_timestamp(): result = get_random_timestamp() try: src.sleep_duration.get_datetime(result) except ValueError: return random_timestamp() else: return result
The new
random_timestampfunction does the followinggenerates a random timestamp by calling
get_random_timestampchecks if the timestamp is good by calling
src.sleep_duration.get_datetimeif the timestamp is good, the function returns it
if the timestamp is bad, it raises ValueError and repeats the process by calling itself
I can add another function to remove some repetition
def random_number(start, end, digits=2): return f'{random.randint(start, end):0{digits}}'
then add calls to it in
get_random_timestampdef get_random_timestamp(): return ( f'{random_number(0,9999,4)}/' f'{random_number(1,12)}/' f'{random_number(1,31)} ' f'{random_number(0,23)}:' f'{random_number(0,59)}' )
all tests are still green
I change
test_duration_w_date_and_timetotest_durationthe terminal shows all tests are still passing
review¶
The challenge was to write a program that calculates the difference between a given wake and sleep time. I ran the following tests to get something that does it
test_datetime_objects where I used python’s online documentation to read about the datetime.datetime.strptime method which I used to change a string to a datetime.datetime object
test_duration_w_date_and_time where I used
random.randint and an how to test that an Exception is raised to generate timestamps with random dates and times that are how to pass values in strings for
wake_timeandsleep_timea while statement to make sure that when
wake_timeis earlier thansleep_timethedurationfunction raises ValueError with a message and returns the right difference between the 2 whenwake_timeis later than or the same assleep_time
I also ran into the following Exceptions
Would you like to write the solution without looking at test_sleep_duration.py?