how to measure sleep duration: test_duration_w_hours_and_minutes


This is II of a program that calculates the difference between a given wake and sleep time.


I want to test the duration function with timestamps where both hours and minutes are random.

RED: make it fail

  • I change the name of test_duration_w_hours

    def test_duration_w_hours_and_minutes(self):
        sleep_time = random_timestamp()
        wake_time = random_timestamp()
    
  • then add a variable for the difference between the hours

    difference_hours = (
        int(wake_time.split(':')[0])
      - int(sleep_time.split(':')[0])
    )
    
    self.assertEqual(
        src.sleep_duration.duration(
            wake_time=wake_time,
            sleep_time=sleep_time
        ),
        difference_hours
    )
    
  • and change the expectation to a timestamp format

    self.assertEqual(
        src.sleep_duration.duration(
            wake_time=wake_time,
            sleep_time=sleep_time
        ),
        f'{difference_hours:02}:00'
    )
    

    the terminal shows AssertionError

    AssertionError: -3 != '-3:00'
    AssertionError: 0 != '00:00'
    AssertionError: 8 != '08:00'
    AssertionError: 16 != '16:00'
    

    the duration function returns a number and the test expects a string. I change it to match the expectation

    def duration(wake_time=None, sleep_time=None):
        difference_hours = (
            get_hour(wake_time)
          - get_hour(sleep_time)
        )
    
        return f'{difference_hours:02}:00'
    
  • then make a copy of difference_hours in the test, change the name, and change 0 to 1 on each line to get the difference between the minutes of wake_time and sleep_time

    def test_duration_w_hours_and_minutes(self):
        sleep_time = random_timestamp()
        wake_time = random_timestamp()
    
        difference_hours = (
            int(wake_time.split(':')[0])
          - int(sleep_time.split(':')[0])
        )
        difference_minutes = (
            int(wake_time.split(':')[1])
          - int(sleep_time.split(':')[1])
        )
    
  • I also add difference_minutes to the expectation

    self.assertEqual(
        src.sleep_duration.duration(
            wake_time=wake_time,
            sleep_time=sleep_time
        ),
        (
            f'{difference_hours:02}:'
            f'{difference_minutes:02}'
        )
    )
    
  • and change the random_timestamp function to have random numbers from 0 up to and including 59 for the minutes

    def random_timestamp():
        return (
            f'{random.randint(0,23):02}:'
            f'{random.randint(0,59):02}'
        )
    

    I get random success when random_timestamp returns 00 for the minutes and AssertionError when it does not

    AssertionError: '-18:00' != '-18:44'
    AssertionError: '05:00' != '05:-16'
    AssertionError: '-2:00' != '-2:-26'
    AssertionError: '16:00' != '16:-25'
    

    the duration function returns 00 for the minutes part of the duration, and the test expects the difference between the minutes of wake_time and sleep_time

GREEN: make it pass

  • I make a copy of difference_hours in duration, change the name, then add it to the return statement

    def duration(wake_time=None, sleep_time=None):
        difference_hours = (
            get_hour(wake_time)
          - get_hour(sleep_time)
        )
        difference_minutes = (
            get_hour(wake_time)
          - get_hour(sleep_time)
        )
    
        return (
            f'{difference_hours:02}:'
            f'{difference_minutes:02}'
        )
    

    the terminal shows AssertionError

    AssertionError: '20:20' != '20:-6'
    AssertionError: '06:06' != '06:17'
    AssertionError: '-16:-16' != '-16:-7'
    AssertionError: '02:02' != '02:07'
    

    the function returns the same numbers for hours and minutes because difference_hours and difference_minutes are the same. I make a copy of the get_hour function, call it get_minutes and change the index to get the second item from the timestamp split

    def get_minutes(timestamp):
        return int(timestamp.split(':')[1])
    

    then change the calls in difference_minutes

    difference_minutes = (
        get_minutes(wake_time)
      - get_minutes(sleep_time)
    )
    

    the test passes! There is something wrong with this calculation…

REFACTOR: make it better

test_duration_calculation

The duration function returns a subtraction of hours and a subtraction of minutes which does not give the right difference between the timestamps

RED: make it fail

If duration is given a wake_time of '03:30' and a sleep_time of '02:59', it should return '00:31' as the difference between the timestamps

def test_duration_calculation(self):
    self.assertEqual(
        src.sleep_duration.duration(
            wake_time='03:30',
            sleep_time='02:59'
        ),
        '00:31'
    )

def test_duration_w_hours_and_minutes(self):
...

the terminal shows AssertionError when I add test_duration_calculation

AssertionError: '01:-29' != '00:31'

duration returns '01:-29' which is not a real duration, the calculation has to change

GREEN: make it pass

  • I add a return statement to the duration function where I multiply difference_hours by 60 then add it to difference_minutes to get the total difference in minutes

    return (
        difference_hours*60
      + difference_minutes
    )
    
    return (
        f'{difference_hours:02}:'
        f'{difference_minutes:02}'
    )
    

    the terminal shows AssertionError for test_duration_w_hours_and_minutes

    AssertionError: -458 != '-7:-38'
    AssertionError: -936 != '-15:-36'
    AssertionError: -31 != '-1:29'
    AssertionError: 213 != '03:33'
    

    duration returns the difference as a number and the test still expects a string. I add the unittest.skip decorator to skip it

    @unittest.skip
    def test_duration_w_hours_and_minutes(self):
    ...
    

    the terminal shows AssertionError for test_duration_calculation

    AssertionError: 31 != '00:31'
    

    the function returns the right number of minutes for the difference. I need a way to change it to hours and minutes to match the expectations of the tests. I add the unittest.skip decorator to skip it while I test the solution

    @unittest.skip
    def test_duration_calculation(self):
    ...
    

    If I divide the total number of minutes by 60, the whole number from the result is the hours and the remainder is the minutes

test_floor_aka_integer_division

The // operator returns a whole number which is how many times the bottom number can be multiplied to get a whole number that is equal to or as close to the top number as possible. It should give me the hours when I divide by 60

  • I add a failing test for it

    def test_floor_aka_integer_division(self):
        self.assertEqual(120//60, 0)
    
    @unittest.skip
    def test_duration_calculation(self):
    ...
    

    the terminal shows AssertionError

    AssertionError: 2 != 0
    

    the result of 120 divided by 60 is 2 with a remainder of 0. I change the expectation to the right value.

    self.assertEqual(120//60, 2)
    

    and it passes

  • I add another assertion

    self.assertEqual(150//60, 0)
    

    the terminal shows AssertionError

    AssertionError: 2 != 0
    

    the result of 150 divided by 60 is also 2 but with a remainder of 30. I change the expectation to the right value

    self.assertEqual(150//60, 2)
    

    the test passes

test_the_modulo_operation

The % operator returns the remainder when a number is divided by another, it should give me the minutes, if I divide by 60

  • I add a failing test for it

    def test_the_modulo_operation(self):
        self.assertEqual(120%60, 2)
    
    @unittest.skip
    def test_duration_calculation(self):
    ...
    

    the terminal shows AssertionError

    AssertionError: 0 != 2
    

    the remainder when 120 is divided by 60 is 0. I change the expectation to the right value

    self.assertEqual(120%60, 0)
    

    the test passes

  • I add another assertion

    self.assertEqual(150%60, 0)
    

    the terminal shows AssertionError

    AssertionError: 30 != 0
    

    the remainder when 150 is divided by 60 is 30. I change the expected value in the test to the right value

    self.assertEqual(150%60, 30)
    

    the test is green again again


  • I comment out the unittest.skip decorator for test_duration_calculation to get back the AssertionError

    # @unittest.skip
    def test_duration_calculation(self):
    
  • and change the first return statement in the duration function to a variable for the total difference in minutes

    difference = (
        difference_hours*60
      + difference_minutes
    )
    
    return (
        f'{difference_hours:02}:'
        f'{difference_minutes:02}'
    )
    

    then add a variable for the hours of the duration using floor (integer) division

    difference = (
        difference_hours*60
      + difference_minutes
    )
    duration_hours = difference // 60
    

    and a variable for the minutes of the duration using the modulo operator

    duration_hours = difference // 60
    duration_minutes = difference % 60
    

    then change difference_hours and difference_minutes in the return statement

    return (
        f'{duration_hours:02}:'
        f'{duration_minutes:02}'
    )
    

    the test passes

  • I remove the unittest.skip decorator from test_duration_calculation

  • and comment it out for test_duration_w_hours_and_minutes

    # @unittest.skip
    def test_duration_w_hours_and_minutes(self):
    ...
    

    the terminal shows random successes and random AssertionError

    AssertionError: '-11:46' != '-10:-14'
    AssertionError: '-6:04' != '-5:-56'
    AssertionError: '10:50' != '11:-10'
    AssertionError: '16:50' != '17:-10'
    

    the calculation in the test is still not right. I change it to match the duration function

    difference = (
        difference_hours*60
      + difference_minutes
    )
    duration_hours = difference // 60
    duration_minutes = difference % 60
    

    then change the variables in the expectation

    self.assertEqual(
        src.sleep_duration.duration(
            wake_time=wake_time,
            sleep_time=sleep_time
        ),
        (
            f'{duration_hours:02}:'
            f'{duration_minutes:02}'
        )
    )
    

    the test passes with no more random failures

  • I take out the unittest.skip decorator from test_duration_w_hours_and_minutes

  • and remove test_duration_calculation because it is covered by test_duration_w_hours_and_minutes which has the right calculation

  • then add a function in sleep_duration.py to change get_hour and get_minutes

    def read_timestamp(timestamp=None, index=0):
        return int(timestamp.split(':')[index])
    
    def duration(wake_time=None, sleep_time=None):
    ...
    
  • call it in duration

    def duration(wake_time=None, sleep_time=None):
        difference_hours = (
            read_timestamp(wake_time)
          - read_timestamp(sleep_time)
        )
        difference_minutes = (
            read_timestamp(wake_time, 1)
          - read_timestamp(sleep_time, 1)
        )
    ...
    
  • and remove get_hour and get_minutes. The terminal shows all tests are still passing!

review

The challenge is to write a program that calculates the difference between a given wake and sleep time. I ran these tests to get something that comes close to doing it

Would you like to test duration with an earlier wake than sleep time?


how to measure sleep duration: tests and solution