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) or option (mac) on the keyboard and use the mouse to click on tests/test_sleep_duration.py:7 to open it in the editor

  • and change True to False

  • 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 module

    self.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 module

    self.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 name

    duration
    

    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 test

    self.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 a wake_time then add it to the test

    self.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 a sleep_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 None

    def 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 expectation

    def 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 and sleep_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 match

    def 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 expectation

    def 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 and sleep_time in the tests every time I have an idea and then change the duration 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 of test_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 including 23 for the 24 hours in a day

  • I interpolate them as hours for wake_time and sleep_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 than 10 it will have a 0 in front of it, for example 01. The terminal shows AssertionError

    AssertionError: 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 inputs

    def 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 and sleep_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 it

    def 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

str.split returns a list when called

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 separator

    self.assertEqual(
        '01:23'.split(':'), ['01', '23']
    )
    

    the test passes which means I know how to get the different parts of wake_time and sleep_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 returns wake_time and sleep_time but the test expects the result of splitting them. I change it to match the expectation

    def 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 index 1. I add a variable to test_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 timestamp

    AssertionError: '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 from wake_time and sleep_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 expectation

    def 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 them

    def 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 to 23

    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 expectation

    def 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 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: (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 the duration function to match the expectation

    def 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 and sleep_hour are only used once in test_sleep_duration.py, I can change them with direct calls to random.randint

    def 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 and sleep_time are defined in the same way, time to make a function that returns a random timestamp

    def 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

I also ran into the following Exceptions

Would you like to test duration with hours and minutes?


how to measure sleep duration: tests and solution