how to measure sleep duration: test_duration_w_hours¶
This is part 1 of a program that calculates the difference between a given wake and sleep time.
red: make it fail¶
I open a terminal to run makePythonTdd.sh with
sleep_duration
as the name of the project./makePythonTdd.sh sleep_duration
on Windows without Windows Subsystem Linux use makePythonTdd.ps1
./makePythonTdd.ps1 sleep_duration
it makes the folders and files that are needed, installs packages, runs the first test, and the terminal shows AssertionError
E AssertionError: True is not false tests/test_sleep_duration.py:7: AssertionError
then I hold
ctrl
(windows/linux) oroption
(mac) on the keyboard and use the mouse to click ontests/test_sleep_duration.py:7
to open it in the editorand change
True
toFalse
I also change the class name to CapWords to match Python convention
class TestSleepDuration(unittest.TestCase):
then change the test to a new failing test
class TestSleepDuration(unittest.TestCase): def test_duration_w_hours(self): self.assertEqual( )
and get TypeError
TypeError: TestCase.assertEqual() missing 2 required positional arguments: 'first' and 'second'
which I add to the list of Exceptions encountered
# Exceptions Encountered # AssertionError # TypeError
green: make it pass¶
I add None as the first and second arguments of the assertion
self.assertEqual( None, None )
then change the first argument to reference the
sleep_duration
moduleself.assertEqual( sleep_duration, None )
which gives me NameError
NameError: name 'sleep_duration' is not defined
and I add it as an exception encountered
# Exceptions Encountered # AssertionError # TypeError # NameError
then add an import statement at the top of the file for the module
import sleep_duration import unittest class TestSleepDuration(unittest.TestCase): ...
the terminal shows AssertionError
AssertionError: <module 'sleep_duration' from '/workspace[46 chars].py'> != None
I add a reference to something in the
sleep_duration
moduleself.assertEqual( sleep_duration.duration, None )
and get AttributeError
AttributeError: module 'sleep_duration' has no attribute 'duration'
I add the error to the list of Exceptions encountered
# Exceptions Encountered # AssertionError # TypeError # NameError # AttributeError
then open
sleep_duration.py
in the editor to add the nameduration
the terminal shows NameError
NameError: name 'duration' is not defined
I point it to None to define it
duration = None
then add a call to
duration
in the testself.assertEqual( sleep_duration.duration(), None )
and get TypeError
TypeError: 'NoneType' object is not callable
I make it callable by changing it to a function
def duration(): return None
I want the
duration
function to take in awake_time
then add it to the testself.assertEqual( sleep_duration.duration( wake_time='08:00' ), None )
then get TypeError
TypeError: duration() got an unexpected keyword argument 'wake_time'
because the name is not in the function’s signature. I add it with a default value of None
def duration(wake_time=None): return None
I also want the
duration
function to take in asleep_time
self.assertEqual( sleep_duration.duration( wake_time='08:00', sleep_time='07:00' ), None )
and get a similar TypeError
TypeError: duration() got an unexpected keyword argument 'sleep_time'
because
sleep_time
is not in the function’s signature. I add it with a default value of Nonedef duration(wake_time=None, sleep_time=None): return None
then set the expectation of the test to the given inputs
self.assertEqual( sleep_duration.duration( wake_time='08:00', sleep_time='07:00' ), ('08:00', '07:00') )
the terminal shows AssertionError
AssertionError: None != ('08:00', '07:00')
the
duration
function returns None, I change it to match the expectationdef duration(wake_time=None, sleep_time=None): return ('08:00', '07:00')
and the test passes
refactor: make it better¶
I add variables to remove the repetition of the values for
wake_time
andsleep_time
def test_duration_w_hours(self): wake_time = '08:00' sleep_time = '07:00' self.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), (wake_time, sleep_time) )
then change
wake_time
def test_duration_w_hours(self): wake_time = '09:00' sleep_time = '07:00'
which gives me AssertionError
AssertionError: Tuples differ: ('08:00', '07:00') != ('09:00', '07:00')
I change
duration
to matchdef duration(wake_time=None, sleep_time=None): return ('09:00', '07:00')
and the test passes
I change
sleep_time
def test_duration_w_hours(self): wake_time = '09:00' sleep_time = '06:00'
and get AssertionError
AssertionError: Tuples differ: ('09:00', '07:00') != ('09:00', '06:00')
then change
duration
to match the expectationdef duration(wake_time=None, sleep_time=None): return ('09:00', '06:00')
and the test passes
I do not want to change the values of
wake_time
andsleep_time
in the tests every time I have an idea and then change theduration
function to match. It would be better to test the function with random numbers. I add an import statement for the random module at the top oftest_sleep_duration.py
import random import sleep_duration import unittest
then add variables for random hours in a day
def test_duration_w_hours(self): wake_hour = random.randint(0, 23) sleep_hour = random.randint(0, 23) wake_time='09:00' sleep_time='06:00' ...
random.randint gives me a random number from
0
up to and including23
for the 24 hours in a dayI interpolate them as hours for
wake_time
andsleep_time
def test_duration_w_hours(self): wake_hour = random.randint(0, 23) sleep_hour = random.randint(0, 23) wake_time=f'{wake_hour:02}:00' sleep_time=f'{sleep_hour:02}:00' ...
the
:02
tells Python to always show the numbers as 2 digits, if it is less than10
it will have a0
in front of it, for example01
. The terminal shows AssertionErrorAssertionError: Tuples differ: ('09:00', '06:00') != ('10:00', '02:00') AssertionError: Tuples differ: ('09:00', '06:00') != ('23:00', '00:00') AssertionError: Tuples differ: ('09:00', '06:00') != ('07:00', '03:00') AssertionError: Tuples differ: ('09:00', '06:00') != ('00:00', '22:00')
duration
still returns('09:00', '06:00')
but the test now uses random timestamps. I change it to return its inputsdef duration(wake_time=None, sleep_time=None): return (wake_time, sleep_time)
and the test passes
I change the expectation of the test to
wake_time-sleep_time
self.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), (wake_time-sleep_time) )
and get TypeError
TypeError: unsupported operand type(s) for -: 'str' and 'str'
the timestamps are strings and I cannot subtract one string from another. I undo the change to go back to what was working
self.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), (wake_time, sleep_time) )
I want to get the hours part of
wake_time
andsleep_time
which are the characters before:
. I add a call to the help system to see which methods of strings can help me break one apart or get specific parts from itdef test_duration_w_hours(self): self.assertEqual(help(str)) ...
the terminal shows python documentation for strings and I read the descriptions until I see a method that looks like what I am looking for
... | | split(self, /, sep=None, maxsplit=-1) | Return a list of the substrings in the string, | using sep as the separator string. | | sep | The separator used to split the string. | ...
I remove
self.assertEqual(help(str))
def test_duration_w_hours(self): wake_hour = random.randint(0, 23) sleep_hour = random.randint(0, 23) ...
test_string_splitting¶
red: make it fail¶
I add a failing test for the str.split method to see what it does
def test_string_splitting(self):
self.assertEqual(
'01:23'.split(), None
)
def test_duration_w_hours(self):
...
the terminal shows AssertionError
AssertionError: ['01:23'] != None
green: make it pass¶
I copy the list from the terminal and paste it in the test to make it pass
self.assertEqual(
'01:23'.split(), ['01:23']
)
green again
refactor: make it better¶
I change the expectation to the hours and minutes as different items
self.assertEqual( '01:23'.split(), ['01', '23'] )
and get AssertionError
AssertionError: Lists differ: ['01:23'] != ['01', '23']
the documentation showed that str.split takes in a separator. I want to see what happens when I pass in
':'
as the separatorself.assertEqual( '01:23'.split(':'), ['01', '23'] )
the test passes which means I know how to get the different parts of
wake_time
andsleep_time
I add calls to the str.split method in
test_duration_w_hours
self.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), ( wake_time.split(':'), sleep_time.split(':') ) )
and the terminal shows AssertionError
AssertionError: Tuples differ: ('00:00', '10:00') != (['00', '00'], ['10', '00']) AssertionError: Tuples differ: ('23:00', '08:00') != (['23', '00'], ['08', '00']) AssertionError: Tuples differ: ('06:00', '11:00') != (['06', '00'], ['11', '00']) AssertionError: Tuples differ: ('13:00', '13:00') != (['13', '00'], ['13', '00'])
the
duration
function returnswake_time
andsleep_time
but the test expects the result of splitting them. I change it to match the expectationdef duration(wake_time=None, sleep_time=None): return ( wake_time.split(':'), sleep_time.split(':') )
and the terminal shows green again
I want the hours part of the timestamp string which is the first item from calling str.split. From the chapter on lists I know I can get it by using its index, Python uses zero-based indexing which means the first item is at index
0
and the second is at index1
. I add a variable totest_string_splitting
def test_string_splitting(self): split = '01:23'.split(':') self.assertEqual(split, ['01', '23'])
the terminal still shows passing tests. I add an assertion for indexing the list
self.assertEqual(split, ['01', '23']) self.assertEqual(split[0], 0)
which gives me AssertionError because the first item (index 0) from splitting
'01:23'
on the separator':'
is'01'
, the hours part of the timestampAssertionError: '01' != 0
I change the value in the test to
'01'
self.assertEqual(split[0], '01')
and it passes
I add another assertion for the minutes
self.assertEqual(split[0], '01') self.assertEqual(split[1], '01')
and get AssertionError
AssertionError: '23' != '01'
the second item (index 1) from splitting
'01:23'
on the separator':'
is'23'
, the minutes part of the timestamp. I change the'01'
to'23'
self.assertEqual(split[1], '23')
and the test passes
I change the expectation of
test_duration_w_hours
to the hours fromwake_time
andsleep_time
self.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), ( wake_time.split(':')[0], sleep_time.split(':')[0] ) )
and get AssertionError
AssertionError: Tuples differ: (['00', '00'], ['19', '00']) != ('00', '19') AssertionError: Tuples differ: (['14', '00'], ['17', '00']) != ('14', '17') AssertionError: Tuples differ: (['05', '00'], ['08', '00']) != ('05', '08') AssertionError: Tuples differ: (['23', '00'], ['04', '00']) != ('23', '04')
the
duration
function returns the result of splitting the timestamps but the test expects the hours, I change it to match the expectationdef duration(wake_time=None, sleep_time=None): return ( wake_time.split(':')[0], sleep_time.split(':')[0] )
and the test passes
test_converting_strings_to_numbers¶
The hours part of the timestamp after calling str.split is still a string and I got the TypeError when I tried to subtract one from another earlier. I want to see if I can use the int constructor to change a string to a number
I add a new failing test to test numbers that have a
0
in front of themdef test_converting_strings_to_numbers(self): self.assertEqual(int('01'), 0) def test_duration_w_hours(self): ...
and the terminal shows AssertionError
AssertionError: 1 != 0
I change the expectation to
1
self.assertEqual(int('01'), 1)
and the test passes
I add another assertion to test a bigger number
self.assertEqual(int('23'), 1)
the terminal shows AssertionError
AssertionError: 23 != 1
I change the number from
1
to23
self.assertEqual(int('23'), 23)
and the terminal shows green again
I add calls to the int constructor in the expectation of
test_duration_w_hours
self.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), ( int(wake_time.split(':')[0]), int(sleep_time.split(':')[0]) ) )
and get AssertionError
AssertionError: Tuples differ: ('00', '05') != (0, 5) AssertionError: Tuples differ: ('08', '21') != (8, 21) AssertionError: Tuples differ: ('04', '04') != (4, 4) AssertionError: Tuples differ: ('16', '14') != (16, 14)
the
duration
function returns the hours as a string but the test expects them as numbers, I change it to match the expectationdef duration(wake_time=None, sleep_time=None): return ( int(wake_time.split(':')[0]), int(sleep_time.split(':')[0]) )
and the test passes
I change the expectation in
test_duration_w_hours
to the difference between the hoursself.assertEqual( sleep_duration.duration( wake_time=wake_time, sleep_time=sleep_time ), ( int(wake_time.split(':')[0]) -int(sleep_time.split(':')[0]) ) )
and get AssertionError
AssertionError: (7, 23) != -16 AssertionError: (11, 4) != 7 AssertionError: (12, 21) != -9 AssertionError: (14, 2) != 12
the
duration
function returns the hours from the timestamps and the test expects the difference between them. I change theduration
function to match the expectationdef duration(wake_time=None, sleep_time=None): return ( int(wake_time.split(':')[0]) - int(sleep_time.split(':')[0]) )
and the terminal shows passing tests! Celebration Time!!
I add a function to get the hours part of a given timestamp since it is the only part that changes in the solution
def get_hour(timestamp): return int(timestamp.split(':')[0])
then call it in
duration
def duration(wake_time=None, sleep_time): return ( get_hour(wake_time) - get_hour(sleep_time) )
and the terminal still shows passing tests!
wake_hour
andsleep_hour
are only used once intest_sleep_duration.py
, I can change them with direct calls to random.randintdef test_duration_w_hours(self): wake_time = f'{random.randint(0,23):02}:00' sleep_time = f'{random.randint(0,23):02}:00' ...
the terminal still shows passing tests
wake_time
andsleep_time
are defined in the same way, time to make a function that returns a random timestampdef random_timestamp(): return f'{random.randint(0,23):02}:00'
and call it in
test_duration_w_hours
def test_duration_w_hours(self): sleep_time = random_timestamp() wake_time = random_timestamp() ...
all tests are still passing! What a beautiful life!!
review¶
The challenge is to write a program that calculates the difference between a given wake and sleep time. I ran the following tests to get something that comes close to doing it
test_string_splitting where I
used the str.split method I found by calling the help system to split a string on a separator
and indexed the list from the split to get specific items
test_duration_w_hours where I
used random.randint to generate random numbers from the 24 hours in a day and how to pass values them in the timestamps
then test that the
duration
function subtracts the hour forsleep_time
from the hour forwake_time
I also ran into the following Exceptions
Would you like to test duration with hours and minutes?