patternjavaMinor
Taxi Meter App Business Logic Using TDD
Viewed 0 times
meterbusinesstaxiapplogicusingtdd
Problem
The following problem has been written using TDD. Please review the following:
Problem Statement:
Taxi Meter Company has to develop an app for their business. The app calculates the bill based on the car type and the distance covered. They have 3 different types of car and they charge the base price for the first 5 miles as
follows:
After they cross 5 miles, the customer will be charged at 10$/mile
for the next 10 miles. Once they cross 15 miles, they will be charged at 20$/mile.
If the total distance covered is greater than 100 miles, they will be charged at 10$/mile for every mile (the total distance). Base price will be added by default in addition to the charges for the miles. Please refer sample input and output.
Sample Input:
Sample Output:
Test cases (Unit tests:)
```
public class TaxiMeterAppSpec {
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
Meter meter = new Meter(5);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(100, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsLightTheBaseFareShouldBe200() {
Meter meter = new Meter(5);
CarType car = new Light(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(200, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showP
- Unit tests
- OOP
- Design Principles (SOLID)
- Cleanliness and naming
- Anything that could make the program better.
Problem Statement:
Taxi Meter Company has to develop an app for their business. The app calculates the bill based on the car type and the distance covered. They have 3 different types of car and they charge the base price for the first 5 miles as
follows:
- Mini - $100
- Light - $200
- Compact - $300
After they cross 5 miles, the customer will be charged at 10$/mile
for the next 10 miles. Once they cross 15 miles, they will be charged at 20$/mile.
If the total distance covered is greater than 100 miles, they will be charged at 10$/mile for every mile (the total distance). Base price will be added by default in addition to the charges for the miles. Please refer sample input and output.
Sample Input:
- Mini, 10 Miles
- Light, 5 Miles
- Mini, 20 Miles
- Compact, 120 Miles
Sample Output:
- 150
- 200
- 300
- 1500
Test cases (Unit tests:)
```
public class TaxiMeterAppSpec {
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
Meter meter = new Meter(5);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(100, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsLightTheBaseFareShouldBe200() {
Meter meter = new Meter(5);
CarType car = new Light(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(200, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showP
Solution
Code structure
I find it odd that a
The idea is that when constructing a
A second point is the usage of an abstract class
The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
and then, all of tests are refactored to:
without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
I find it odd that a
CarType would know the distance it traveled. This class is supposed to model the type of the car, and this does not depend on the distance: a CarType is the same whether you traveled 100 or 1000 miles with it. What really depend on the distance traveled is the price; therefore, I'd refactor the method showPrice to take as parameter the meter:public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}The idea is that when constructing a
TaxiMeterApp, you don't know in advance the distance the person is going to travel, only the car type they chose. And note that Bill constructor would now take 2 parameters: the car type and the miles traveled, which is all it needs to derive the price.A second point is the usage of an abstract class
CarType with multiple implementations. If you intend in the future to let users of the API create new CarTypes, this is a good solution; if not, consider having an enumeration, which will be simpler:public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}and then, all of tests are refactored to:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
Code Snippets
public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}Context
StackExchange Code Review Q#145008, answer score: 3
Revisions (0)
No revisions yet.