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
datetime
librarydatetime.now().year
self
prefix. E.g.
self.currentyear()
57-exercises-python/src/exercises/Ex06_retirement_calculator