how to measure sleep duration: test_duration_w_date_and_time
This is IV 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 is my friend, and 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 usesint()to change the timestamp string to a number after it calls str.split, and 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 is my friend, and 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
int()to change a timestamp string to a number when it has a date. I add an assertRaises to handle the ValueErrordef 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 is my friend, and 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 Nonethe terminal is my friend, and 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 usesint()I 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 is my friend, and 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 is my friend, and 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 is my friend, and 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 is my friend, and 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, if 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 is my friend, and 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 is my friend, and 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 test is 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 is my friend, and 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 assertion 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 is my friend, and 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 is my friend, and 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 test is 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 is my friend, and 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 assertion 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
str()to the expectation in the test because I want the result as a string not a `datetime.timedelta`_ objectdef 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 is my friend, and 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 is my friend, and 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 these 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 is my friend, and shows a random ValueError
ValueError: day is out of range for monthI 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 resultThe new
random_timestampfunction does these thingsgenerates 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 is my friend, and 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 these 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, ifwake_timeis later than or the same assleep_time
I also saw these Exceptions
Would you like to write the solution without looking at test_sleep_duration.py?