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_minutes
and change the name totest_duration_w_date_and_time
then add calls to
random_timestamp_a
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, sleep_time ) wake_time = random_timestamp_a() else: self.assertEqual( sleep_duration.duration( sleep_time=sleep_time, wake_time=wake_time ), self.get_difference( wake_time=wake_time, sleep_time=sleep_time ) )
which gives me 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_numbers
to 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_time
ValueError: 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_time
then 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( sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), self.get_difference( wake_time=wake_time, sleep_time=sleep_time ) )
which gives me AttributeError
AttributeError: module 'sleep_duration' has no attribute 'duration_a'...
I make a copy of the
duration
function insleep_duration.py
and change the name toduration_a
to keep the working solution while I try a new one. I change theelse
block 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
and the terminal shows ValueError
ValueError: invalid literal for int() with base 10: '1999/12/31 22'
because the test calls the
get_difference
method in the expectation which uses the int constructorI change it to return
wake_time
andsleep_time
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( sleep_time=sleep_time, wake_time=wake_time, ) wake_time = random_timestamp_a() else: self.assertEqual( sleep_duration.duration_a( wake_time=wake_time, sleep_time=sleep_time ), (wake_time, sleep_time) )
and get 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_a
def 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)
and 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):
...
and 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 sleep_duration
import unittest
...
and 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"
),
''
)
and get AssertionError
AssertionError: datetime.datetime(2006, 11, 21, 16, 30) != ''
I copy the value from the left side of the AssertionError to change 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
%d
for days%m
for months%y
for 2 digit years%H
for hours%M
for 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 ) )
and get 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 ) )
which gives me 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
%Y
for 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_time
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( 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' ) ) )
and get 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_a
returns 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' ) )
and 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.py
import datetime def read_timestamp(timestamp=None, index=0): ...
and 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_a
def 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 reference sleep_duration.get_datetime
which calls the datetime.datetime.strptime method
def test_get_datetime(self):
self.assertEqual(
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(
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(
sleep_duration.get_datetime(
timestamp
),
datetime.datetime.strptime(
'2006/11/21 16:30',
'%Y/%m/%d %H:%M'
)
)
and get 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(
sleep_duration.get_datetime(
timestamp
),
datetime.datetime.strptime(
timestamp,
'%Y/%m/%d %H:%M'
)
)
and the terminal shows green again
refactor: make it better¶
I change the calls to datetime.datetime.strptime to 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(
sleep_duration.duration_a(
sleep_time=sleep_time,
wake_time=wake_time
),
(
sleep_duration.get_datetime(
wake_time
),
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( sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), ( sleep_duration.get_datetime( wake_time ) - sleep_duration.get_datetime( sleep_time ) ) )
and get 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_a
function 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) )
and 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( sleep_duration.duration_a( sleep_time=sleep_time, wake_time=wake_time ), str( sleep_duration.get_datetime( wake_time ) - sleep_duration.get_datetime( sleep_time ) ) )
and 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_a
def 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
duration
becauseduration_a
is a better solutionwhich means I can remove
read_timestamp
because no one calls it anymore. The terminal shows AttributeErrorAttributeError: module 'sleep_duration' has no attribute 'duration'...
I change the name of
duration_a
toduration
insleep_duration.py
andtest_sleep_duration.py
which 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_minutes
does not have dates in its timestamps. I remove it because it is covered bytest_duration_w_date_and_time
then remove
get_difference
because no one calls it anymoreI also remove the following tests because they do not test the solution directly
test_the_modulo_operation
test_floor_aka_integer_division
test_converting_strings_to_numbers
test_string_splitting
and remove
random_timestamp
because no one calls it anymorethen change the name of
random_timestamp_a
torandom_timestamp
the new
random_timestamp
function 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}' )
and get 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_timestamp
toget_random_timestamp
and 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: sleep_duration.get_datetime(result) except ValueError: return random_timestamp() else: return result
The new
random_timestamp
function does the followinggenerates a random timestamp by calling
get_random_timestamp
checks if the timestamp is good by calling
sleep_duration.get_datetime
if 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_timestamp
def 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_time
totest_duration
and the 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_time
andsleep_time
a while statement to make sure that when
wake_time
is earlier thansleep_time
theduration
function raises ValueError with a message and returns the right difference between the 2 whenwake_time
is 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?