Tim Cooke

Exercises for Programmers (Python): 6 of 57 - Retirement Calculator

30th August 2020

Exercise 6 in book Exercises for Programmers: 57 Challenges to Develop Your Coding Skills by Brian P. Hogan.

Create a program that determines how many ears you have left until retirement and the year you can retire. It should prompt for your current age and the age you want to retire and display the output as shown in the example that follows.

Example Output

What is your current age? 25
At what age would you like to retire? 65
You have 40 years left until you can retire.
It's 2015, so you can retire in 2055

This exercise is all about using your programming language to interrogate the operating system for the current date. I expect this will be relatively straight forward, but the real challenge in this exercise comes with writing the unit tests. Testing code who's behaviour is dependent on the system clock is really difficult because you have no control over its value. For example, a test that asserts that 2 years from now is 2022 will only be valid while the year is 2020, and as soon as it becomes 2021 the test is invalid and will fail.

This leads me into thinking about how to gain control of the system clock from within my tests. Perhaps there are mock tools available for Python, or maybe I can leverage the fact that Python is an Object Oriented language for a simpler solution. Let's see.

First I'll use some tests to drive the implementation of the easy bits.

class TestRetirementCalculator(unittest.TestCase):

    def test_givenCurrentAge_andRetirementAge_thenCalculateYearsLeft(self):
        self.assertEqual(retirement_calculator.yearsleft(25, 65), 40)
    
    def test_givenCurrentYear_andYearsLeft_thenCalculateRetirementYear(self):
        self.assertEqual(retirement_calculator.retirementyear(2016, 40), 2056)
def yearsleft(currentage, retirementage):
    return retirementage - currentage
    
    
def retirementyear(currentYear, yearsLeft):
    return currentYear + yearsLeft


if __name__ == "__main__":
    currentage = input("What is your current age? ")
    retirementage = input("At what age would you like to retire? ")
    yearsleft = yearsleft(int(currentage), int(retirementage))
    print("You have " + str(yearsleft) + " years left until you can retire.")

So far so good.

$ python3 exercises/Ex06_retirement_calculator/retirement_calculator.py 
What is your current age? 25
At what age would you like to retire? 65
You have 40 years left until you can retire.

Now, how to get that current year, and those tricky unit tests. Geting the current year turns out to be pretty easy.

from datetime import datetime
datetime.now().year

So far I've only been working with Python as a scripting language and it's now time to progress into using its Object Oriented features. When programming in Java I might create a test subclass of the class under test to override a particular method in order to inject test data into the class. In order to try that out I have to refactor my script style code into OO style.

from datetime import datetime


class RetirementCalculator:

    def yearsleft(self, currentage, retirementage):
        return retirementage - currentage

    def retirementyear(self, yearsLeft):
        return self.currentyear() + yearsLeft

    def currentyear(self):
        return datetime.now().year


if __name__ == "__main__":
    currentage = input("What is your current age? ")
    retirementage = input("At what age would you like to retire? ")
    retirementCalculator = RetirementCalculator()
    yearsleft = retirementCalculator.yearsleft(int(currentage), int(retirementage))
    print("You have " + str(yearsleft) + " years left until you can retire.")
    print("It's " + str(retirementCalculator.currentyear()) + ", so you can retire in " + str(
        retirementCalculator.retirementyear(yearsleft)))

The difficulty in testing this class is that because it relies on the system time it is non-deterministic, meaning I cannot guarantee its output for a given input, due to the external dependency on the system time. To make the class more testable I have extracted the system time interaction into its own function with the intention of creating a test double within my test file that overrides that function and returns a specific value for the tests.

import unittest

from exercises.Ex06_retirement_calculator.retirement_calculator import RetirementCalculator


class TestRetirementCalculator(RetirementCalculator):

    def currentyear(self):
        return 2016


class TestRetirementCalculator(unittest.TestCase):
    retirementCalculator = TestRetirementCalculator()

    def test_givenCurrentAge_andRetirementAge_thenCalculateYearsLeft(self):
        self.assertEqual(self.retirementCalculator.yearsleft(25, 65), 40)

    def test_givenCurrentYear_andYearsLeft_thenCalculateRetirementYear(self):
        self.assertEqual(self.retirementCalculator.retirementyear(40), 2056)

From running all the unit tests along with some manual testing it appears to have turn out nicely

$ python3 -m unittest discover -s exercises/Ex06_retirement_calculator/
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
$ python3 exercises/Ex06_retirement_calculator/retirement_calculator.py 
What is your current age? 41
At what age would you like to retire? 65
You have 24 years left until you can retire.
It's 2020, so you can retire in 2044

Learning review

Github

57-exercises-python/src/exercises/Ex06_retirement_calculator