From ac2a0788b9924d3e7a6d06e17284c952aed96859 Mon Sep 17 00:00:00 2001 From: Ashish Vaghela Date: Wed, 28 Aug 2024 19:54:44 +0530 Subject: [PATCH 1/6] `ExpenseReport` feature file added --- .../src/test/resources/ExpenseReport.feature | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 expensereport-java/src/test/resources/ExpenseReport.feature diff --git a/expensereport-java/src/test/resources/ExpenseReport.feature b/expensereport-java/src/test/resources/ExpenseReport.feature new file mode 100644 index 00000000..5553d4c9 --- /dev/null +++ b/expensereport-java/src/test/resources/ExpenseReport.feature @@ -0,0 +1,14 @@ +Feature: ExpenseReport + + Scenario: one breakfast + Given the following expenses: + | Type | Amount | + | BREAKFAST | 100 | + When printing the report + Then the report MUST look like this: + """ + Expenses + breakfast 100 + Meal expenses: 100 + Total expenses: 100 + """ From c48dda3b69e30ea916a5898f0b0435a3232112d7 Mon Sep 17 00:00:00 2001 From: Ashish Vaghela Date: Wed, 28 Aug 2024 19:59:34 +0530 Subject: [PATCH 2/6] Implement `Cucumber step definitions` for ExpenseReport --- .../ExpenseReportStepDefinitions.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java diff --git a/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java b/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java new file mode 100644 index 00000000..09316111 --- /dev/null +++ b/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java @@ -0,0 +1,48 @@ +package com.nelkinda.training; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ExpenseReportStepDefinitions { + private List expenses; + private ByteArrayOutputStream outputStream; + + @Given("the following expenses:") + public void the_following_expenses(List> expenseData) { + expenses = new ArrayList<>(); + for (List row : expenseData) { + Expense expense = new Expense(); + expense.type = ExpenseType.valueOf(row.get(0).toUpperCase()); + expense.amount = Integer.parseInt(row.get(1)); + expenses.add(expense); + } + } + + @When("printing the report") + public void printing_the_report() { + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + ExpenseReport report = new ExpenseReport(); + report.printReport(expenses); + System.setOut(System.out); + } + + @Then("the report MUST look like this:") + public void the_report_must_look_like_this(String expectedOutput) { + String actualOutput = outputStream.toString().trim(); + String currentDate = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").format(new Date()); + String expectedWithDate = expectedOutput.replace("", currentDate).trim(); + + assertEquals(expectedWithDate, actualOutput); + } +} From 07c6736c8ddfbbc02d80fbbe6ca24ba428231339 Mon Sep 17 00:00:00 2001 From: Ashish Vaghela Date: Wed, 28 Aug 2024 20:00:24 +0530 Subject: [PATCH 3/6] Refactor `ExpenseReport` class to separate concerns and apply SOLID principles --- .../com/nelkinda/training/ExpenseReport.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java index b85ee9af..9986d4b4 100644 --- a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java +++ b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java @@ -12,7 +12,7 @@ class Expense { int amount; } -public class ExpenseReport { +class ExpenseReport { public void printReport(List expenses) { int total = 0; int mealExpenses = 0; @@ -24,22 +24,16 @@ public void printReport(List expenses) { mealExpenses += expense.amount; } - String expenseName = ""; - switch (expense.type) { - case DINNER: - expenseName = "Dinner"; - break; - case BREAKFAST: - expenseName = "Breakfast"; - break; - case CAR_RENTAL: - expenseName = "Car Rental"; - break; - } + String expenseName = switch (expense.type) { + case DINNER -> "dinner"; + case BREAKFAST -> "breakfast"; + case CAR_RENTAL -> "car rental"; + }; - String mealOverExpensesMarker = expense.type == ExpenseType.DINNER && expense.amount > 5000 || expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 ? "X" : " "; + String mealOverExpensesMarker = expense.type == ExpenseType.DINNER && expense.amount > 5000 || + expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 ? "X" : " "; - System.out.println(expenseName + "\t" + expense.amount + "\t" + mealOverExpensesMarker); + System.out.println(expenseName + " " + expense.amount + " " + mealOverExpensesMarker); total += expense.amount; } From d29726d580fe0b8741b21fa9a5c258b91fde3931 Mon Sep 17 00:00:00 2001 From: Ashish Vaghela Date: Wed, 28 Aug 2024 20:06:55 +0530 Subject: [PATCH 4/6] Add scenarios for `expenses with some exceeding limits` --- .../src/test/resources/ExpenseReport.feature | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/expensereport-java/src/test/resources/ExpenseReport.feature b/expensereport-java/src/test/resources/ExpenseReport.feature index 5553d4c9..4b485fff 100644 --- a/expensereport-java/src/test/resources/ExpenseReport.feature +++ b/expensereport-java/src/test/resources/ExpenseReport.feature @@ -1,14 +1,31 @@ Feature: ExpenseReport - Scenario: one breakfast + Scenario: single breakfast expense Given the following expenses: | Type | Amount | | BREAKFAST | 100 | - When printing the report - Then the report MUST look like this: + When printing the report + Then the report MUST look like this: """ Expenses - breakfast 100 + breakfast 100 Meal expenses: 100 Total expenses: 100 """ + + Scenario: multiple expenses with some exceeding limits + Given the following expenses: + | Type | Amount | + | BREAKFAST | 1200 | + | DINNER | 6000 | + | CAR_RENTAL | 3000 | + When printing the report + Then the report MUST look like this: + """ + Expenses + breakfast 1200 X + dinner 6000 X + car rental 3000 + Meal expenses: 7200 + Total expenses: 10200 + """ From dee1862eb30ce90b856e2dfde5da7baa7d1c5127 Mon Sep 17 00:00:00 2001 From: Ashish Vaghela Date: Wed, 28 Aug 2024 20:07:20 +0530 Subject: [PATCH 5/6] Implement step definitions for `expenses with some exceeding limits` --- .../training/ExpenseReportStepDefinitions.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java b/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java index 09316111..5afe1f2a 100644 --- a/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java +++ b/expensereport-java/src/test/java/com/nelkinda/training/ExpenseReportStepDefinitions.java @@ -16,6 +16,7 @@ public class ExpenseReportStepDefinitions { private List expenses; private ByteArrayOutputStream outputStream; + private ExpenseReport report; @Given("the following expenses:") public void the_following_expenses(List> expenseData) { @@ -31,10 +32,15 @@ public void the_following_expenses(List> expenseData) { @When("printing the report") public void printing_the_report() { outputStream = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; System.setOut(new PrintStream(outputStream)); - ExpenseReport report = new ExpenseReport(); + + ExpenseFormatter formatter = new SimpleExpenseFormatter(); + ReportPrinter printer = new ReportPrinter(formatter, System.out); + report = new ExpenseReport(printer); report.printReport(expenses); - System.setOut(System.out); + + System.setOut(originalOut); } @Then("the report MUST look like this:") From 60a39cd96bcdc6af31e42c4feec3fbea10f0fbe2 Mon Sep 17 00:00:00 2001 From: Ashish Vaghela Date: Wed, 28 Aug 2024 20:16:47 +0530 Subject: [PATCH 6/6] Refactor `ExpenseReport` and related classes to apply SOLID principles and improve readability --- expensereport-java/src/ReportPrinter.java | 33 +++++++++++++++ .../java/com/nelkinda/training/Expense.java | 6 +++ .../nelkinda/training/ExpenseFormatter.java | 7 ++++ .../com/nelkinda/training/ExpenseReport.java | 41 +++---------------- .../com/nelkinda/training/ExpenseType.java | 5 +++ .../com/nelkinda/training/ReportPrinter.java | 33 +++++++++++++++ .../training/SimpleExpenseFormatter.java | 28 +++++++++++++ 7 files changed, 118 insertions(+), 35 deletions(-) create mode 100644 expensereport-java/src/ReportPrinter.java create mode 100644 expensereport-java/src/main/java/com/nelkinda/training/Expense.java create mode 100644 expensereport-java/src/main/java/com/nelkinda/training/ExpenseFormatter.java create mode 100644 expensereport-java/src/main/java/com/nelkinda/training/ExpenseType.java create mode 100644 expensereport-java/src/main/java/com/nelkinda/training/ReportPrinter.java create mode 100644 expensereport-java/src/main/java/com/nelkinda/training/SimpleExpenseFormatter.java diff --git a/expensereport-java/src/ReportPrinter.java b/expensereport-java/src/ReportPrinter.java new file mode 100644 index 00000000..e293bf0d --- /dev/null +++ b/expensereport-java/src/ReportPrinter.java @@ -0,0 +1,33 @@ +package com.nelkinda.training; + +import java.io.PrintStream; +import java.util.List; + +public class ReportPrinter { + private final ExpenseFormatter formatter; + private final PrintStream output; + + public ReportPrinter(ExpenseFormatter formatter, PrintStream output) { + this.formatter = formatter; + this.output = output; + } + + public void printReport(List expenses) { + int total = 0; + int mealExpenses = 0; + + output.println(formatter.formatHeader()); + + for (Expense expense : expenses) { + if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) { + mealExpenses += expense.amount; + } + + output.println(formatter.format(expense)); + + total += expense.amount; + } + + output.println(formatter.formatFooter(mealExpenses, total)); + } +} diff --git a/expensereport-java/src/main/java/com/nelkinda/training/Expense.java b/expensereport-java/src/main/java/com/nelkinda/training/Expense.java new file mode 100644 index 00000000..d13d8cf2 --- /dev/null +++ b/expensereport-java/src/main/java/com/nelkinda/training/Expense.java @@ -0,0 +1,6 @@ +package com.nelkinda.training; + +public class Expense { + public ExpenseType type; + public int amount; +} diff --git a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseFormatter.java b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseFormatter.java new file mode 100644 index 00000000..bcf9a79c --- /dev/null +++ b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseFormatter.java @@ -0,0 +1,7 @@ +package com.nelkinda.training; + +public interface ExpenseFormatter { + String format(Expense expense); + String formatHeader(); + String formatFooter(int mealExpenses, int totalExpenses); +} diff --git a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java index 9986d4b4..edda2a4a 100644 --- a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java +++ b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseReport.java @@ -1,44 +1,15 @@ package com.nelkinda.training; -import java.util.Date; import java.util.List; -enum ExpenseType { - DINNER, BREAKFAST, CAR_RENTAL -} +public class ExpenseReport { + private final ReportPrinter printer; -class Expense { - ExpenseType type; - int amount; -} + public ExpenseReport(ReportPrinter printer) { + this.printer = printer; + } -class ExpenseReport { public void printReport(List expenses) { - int total = 0; - int mealExpenses = 0; - - System.out.println("Expenses " + new Date()); - - for (Expense expense : expenses) { - if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) { - mealExpenses += expense.amount; - } - - String expenseName = switch (expense.type) { - case DINNER -> "dinner"; - case BREAKFAST -> "breakfast"; - case CAR_RENTAL -> "car rental"; - }; - - String mealOverExpensesMarker = expense.type == ExpenseType.DINNER && expense.amount > 5000 || - expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 ? "X" : " "; - - System.out.println(expenseName + " " + expense.amount + " " + mealOverExpensesMarker); - - total += expense.amount; - } - - System.out.println("Meal expenses: " + mealExpenses); - System.out.println("Total expenses: " + total); + printer.printReport(expenses); } } diff --git a/expensereport-java/src/main/java/com/nelkinda/training/ExpenseType.java b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseType.java new file mode 100644 index 00000000..3fc43dea --- /dev/null +++ b/expensereport-java/src/main/java/com/nelkinda/training/ExpenseType.java @@ -0,0 +1,5 @@ +package com.nelkinda.training; + +public enum ExpenseType { + DINNER, BREAKFAST, CAR_RENTAL +} diff --git a/expensereport-java/src/main/java/com/nelkinda/training/ReportPrinter.java b/expensereport-java/src/main/java/com/nelkinda/training/ReportPrinter.java new file mode 100644 index 00000000..e293bf0d --- /dev/null +++ b/expensereport-java/src/main/java/com/nelkinda/training/ReportPrinter.java @@ -0,0 +1,33 @@ +package com.nelkinda.training; + +import java.io.PrintStream; +import java.util.List; + +public class ReportPrinter { + private final ExpenseFormatter formatter; + private final PrintStream output; + + public ReportPrinter(ExpenseFormatter formatter, PrintStream output) { + this.formatter = formatter; + this.output = output; + } + + public void printReport(List expenses) { + int total = 0; + int mealExpenses = 0; + + output.println(formatter.formatHeader()); + + for (Expense expense : expenses) { + if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) { + mealExpenses += expense.amount; + } + + output.println(formatter.format(expense)); + + total += expense.amount; + } + + output.println(formatter.formatFooter(mealExpenses, total)); + } +} diff --git a/expensereport-java/src/main/java/com/nelkinda/training/SimpleExpenseFormatter.java b/expensereport-java/src/main/java/com/nelkinda/training/SimpleExpenseFormatter.java new file mode 100644 index 00000000..f5d4e3fd --- /dev/null +++ b/expensereport-java/src/main/java/com/nelkinda/training/SimpleExpenseFormatter.java @@ -0,0 +1,28 @@ +package com.nelkinda.training; + +public class SimpleExpenseFormatter implements ExpenseFormatter { + + @Override + public String format(Expense expense) { + String expenseName = switch (expense.type) { + case DINNER -> "dinner"; + case BREAKFAST -> "breakfast"; + case CAR_RENTAL -> "car rental"; + }; + + String mealOverExpensesMarker = expense.type == ExpenseType.DINNER && expense.amount > 5000 || + expense.type == ExpenseType.BREAKFAST && expense.amount > 1000 ? "X" : " "; + + return String.format("%s %d %s", expenseName, expense.amount, mealOverExpensesMarker); + } + + @Override + public String formatHeader() { + return "Expenses " + new java.util.Date(); + } + + @Override + public String formatFooter(int mealExpenses, int totalExpenses) { + return String.format("Meal expenses: %d\nTotal expenses: %d", mealExpenses, totalExpenses); + } +}