Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ To see solutions, switch to the [solutions branch](https://github.com/christianh

## Credits and License
I first encountered the ExpenseReport example during a bootcamp at Equal Experts.
I also have seen the ExpenseReport example being used by Robert "Uncle Bob" C. Martin.
I also have seen the [ExpenseReport example](https://github.com/unclebob/Episode-10-ExpenseReport) being used by Robert "Uncle Bob" C. Martin.
However, he seems to not be the original author (https://twitter.com/unclebobmartin/status/1537063143326855176?s=20&t=lh_vVb9jUQmY6PYG50974w)
I have tried to research its origins but so far I have failed.
If you know who has first come up with this example, please get in touch with me.
Expand Down
33 changes: 33 additions & 0 deletions expensereport-java/src/ReportPrinter.java
Original file line number Diff line number Diff line change
@@ -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<Expense> 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.nelkinda.training;

public record Expense(ExpenseType type, int amount) {
public boolean isMeal() {
return type.isMeal();
}

public boolean exceedsLimit() {
return type.exceedsLimit(amount);
}

public String getName() {
return type.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.nelkinda.training;

public interface ExpenseFormatter {
String format(Expense expense);
String formatHeader();
String formatFooter(int mealExpenses, int totalExpenses);
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,15 @@
package com.nelkinda.training;

import java.util.Date;
import java.util.List;

enum ExpenseType {
DINNER, BREAKFAST, CAR_RENTAL
}

class Expense {
ExpenseType type;
int amount;
}

public class ExpenseReport {
public void printReport(List<Expense> expenses) {
int total = 0;
int mealExpenses = 0;

System.out.println("Expenses " + new Date());
private final ReportPrinter printer;

for (Expense expense : expenses) {
if (expense.type == ExpenseType.DINNER || expense.type == ExpenseType.BREAKFAST) {
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 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);

total += expense.amount;
}
public ExpenseReport(ReportPrinter printer) {
this.printer = printer;
}

System.out.println("Meal expenses: " + mealExpenses);
System.out.println("Total expenses: " + total);
public void printReport(List<Expense> expenses) {
printer.printReport(expenses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.nelkinda.training;

public abstract class ExpenseType {
public abstract String getName();
public abstract boolean isMeal();
public abstract boolean exceedsLimit(int amount);

public static final ExpenseType DINNER = new ExpenseType() {
@Override
public String getName() {
return "dinner";
}

@Override
public boolean isMeal() {
return true;
}

@Override
public boolean exceedsLimit(int amount) {
return amount > 5000;
}
};

public static final ExpenseType BREAKFAST = new ExpenseType() {
@Override
public String getName() {
return "breakfast";
}

@Override
public boolean isMeal() {
return true;
}

@Override
public boolean exceedsLimit(int amount) {
return amount > 1000;
}
};

public static final ExpenseType CAR_RENTAL = new ExpenseType() {
@Override
public String getName() {
return "car rental";
}

@Override
public boolean isMeal() {
return false;
}

@Override
public boolean exceedsLimit(int amount) {
return false; // No limit for car rental
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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<Expense> expenses) {
int total = 0;
int mealExpenses = 0;

output.println(formatter.formatHeader());

for (Expense expense : expenses) {
if (expense.isMeal()) {
mealExpenses += expense.amount();
}

output.println(formatter.format(expense));
total += expense.amount();
}

output.println(formatter.formatFooter(mealExpenses, total));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nelkinda.training;

public class SimpleExpenseFormatter implements ExpenseFormatter {

@Override
public String format(Expense expense) {
String mealOverExpensesMarker = expense.exceedsLimit() ? "X" : " ";
return String.format("%s %d %s", expense.getName(), 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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<Expense> expenses;
private ByteArrayOutputStream outputStream;
private ExpenseReport report;

@Given("the following expenses:")
public void the_following_expenses(List<List<String>> expenseData) {
expenses = new ArrayList<>();
for (List<String> 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();
PrintStream originalOut = System.out;
System.setOut(new PrintStream(outputStream));

ExpenseFormatter formatter = new SimpleExpenseFormatter();
ReportPrinter printer = new ReportPrinter(formatter, System.out);
report = new ExpenseReport(printer);
report.printReport(expenses);

System.setOut(originalOut);
}

@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("<current date>", currentDate).trim();

assertEquals(expectedWithDate, actualOutput);
}
}
31 changes: 31 additions & 0 deletions expensereport-java/src/test/resources/ExpenseReport.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Feature: ExpenseReport

Scenario: single breakfast expense
Given the following expenses:
| Type | Amount |
| BREAKFAST | 100 |
When printing the report
Then the report MUST look like this:
"""
Expenses <current date>
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 <current date>
breakfast 1200 X
dinner 6000 X
car rental 3000
Meal expenses: 7200
Total expenses: 10200
"""