Practice the Open-Closed Principle by refactoring an expense report without changing its behavior. Setup is complete when all four starter tests pass.
This kata complements Clean Code: SOLID, Ep. 10 - Open-Closed Principle.
In this exercise, you'll practice refactoring code to adhere to the Open-Closed Principle (OCP). You'll be working with an expense report system that tracks different types of expenses and prints a final report.
The ExpenseReport class in the expense_report.py file generates an expense
report. It iterates over a list of expenses, printing out a line for each
expense and totaling up the amounts.
The current implementation, however, is complex and hard to understand, and it's not easy to modify or extend its behavior. It's your task to refactor this code to make it cleaner and more maintainable, while ensuring that it still correctly calculates and prints the report.
Make sure the program still behaves the same way after your refactoring. The unit test suite checks this at a rudimentary level by examining only the program's output.
When you're done refactoring, test the quality of your refactoring by implementing two additional scenarios:
-
Extend our system to handle two more types of expenses.
- Transportation. Surcharge: 5% of the expense amount.
- Supplies. Surcharge: No surcharge.
-
Add dynamic surcharges based on the day of the week.
- For dinners on weekends, the surcharge is 15% of the expense amount.
- For breakfasts on weekends, the surcharge is 10% of the expense amount.
- On weekdays, the surcharges remain the same as before (10% for dinner and 5% for breakfast).
You may need to add a
datefield to theExpenseclass to support this requirement. The field should record when the expense occurred.
Tip
You can use the datetime module in Python to work with dates, and you
can determine if a date is a weekend by using the following function:
from datetime import datetime
def is_weekend(date_str):
date = datetime.strptime(date_str, "%Y-%m-%d")
day_of_week = date.weekday()
# If day of week is 5 or 6 (Saturday or Sunday), it's a weekend
return day_of_week >= 5
date_str = "2023-06-17" # this is a Saturday
print(is_weekend(date_str)) # this should print: TrueRequired:
Optional:
- GNU Make, for shorter commands. Every required task also
has a direct
uvcommand.
You do not need to install Python or pytest separately. uv installs a compatible Python version
and the locked project dependencies when needed.
-
Clone the repository:
git clone https://github.com/Coding-Cuddles/expense-report-refactoring-python-kata.git -
Enter the repository directory:
cd expense-report-refactoring-python-kata -
Run the tests. Use Make when it is installed:
make testOtherwise, run pytest through
uvdirectly:uv run pytestThe first run may install Python and the project dependencies. Setup is complete when pytest reports
4 passed.If the command fails with
uv: command not found, install uv and repeat this step.
-
Refactor
ExpenseReportinexpense_report.pywithout changing the report output. -
Run the tests after each change. Use Make when it is installed:
make testOtherwise, run pytest through
uvdirectly:uv run pytestContinue when the test run completes without failures.
Make is optional. Run make or make help to list these commands in the terminal.
| Command | Result |
|---|---|
make all |
Run the test suite |
make help |
Show the command reference |
make test |
Run the test suite |
make format |
Format tracked Python files |
make format-check |
Check formatting without changing files |
make clean |
Remove generated caches |