Tests and business logic basic tests, how it should be.

This commit is contained in:
Paweł Dyda 2022-11-17 22:15:54 +01:00
parent 9d064a5a36
commit 6e9541958e
15 changed files with 452 additions and 90 deletions

View File

@ -27,7 +27,7 @@ public class Person {
var baseDifference = now.getYear() - birthYear; var baseDifference = now.getYear() - birthYear;
if (birthDate.getMonthValue() > now.getMonthValue() || if (birthDate.getMonthValue() > now.getMonthValue() ||
(birthDate.getMonthValue() == now.getMonthValue() && (birthDate.getMonthValue() == now.getMonthValue() &&
birthDate.getDayOfMonth() >= now.getDayOfMonth())) { birthDate.getDayOfMonth() <= now.getDayOfMonth())) {
return baseDifference; return baseDifference;
} }
return baseDifference - 1; return baseDifference - 1;

View File

@ -0,0 +1,9 @@
package pl.amu.edu.demo.logic;
public class AgeDecider {
public boolean isEligible(int age) {
return age >= 35 && age < 65;
}
}

View File

@ -5,16 +5,18 @@ import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import pl.amu.edu.demo.data.Housing; import pl.amu.edu.demo.data.Housing;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult; import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import pl.amu.edu.demo.providers.AverageQuoteProvider;
@RequiredArgsConstructor @RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class ApartmentHousingStrategy implements HousingStrategy { public class ApartmentHousingStrategy implements HousingStrategy {
AverageQuoteProvider provider; NetWorthCalculator calculator;
NetWorthDecider decider;
@Override @Override
public WeWillStealYourFlatCheckResult checkEligibility(Housing housing) { public WeWillStealYourFlatCheckResult checkEligibility(Housing housing) {
return WeWillStealYourFlatCheckResult.ELIGIBLE; var netWorth = calculator.calculateNetWorthForZipCode(housing.address.zipCode, housing.area);
return decider.isEligible(netWorth);
} }
} }

View File

@ -2,14 +2,13 @@ package pl.amu.edu.demo.logic;
import pl.amu.edu.demo.data.Housing; import pl.amu.edu.demo.data.Housing;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult; import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import pl.amu.edu.demo.providers.AverageQuoteProvider;
public interface HousingStrategy { public interface HousingStrategy {
WeWillStealYourFlatCheckResult checkEligibility(Housing housing); WeWillStealYourFlatCheckResult checkEligibility(Housing housing);
static HousingStrategy getInstance(Housing housing, AverageQuoteProvider provider) { static HousingStrategy getInstance(Housing housing, NetWorthCalculator calculator, NetWorthDecider decider) {
return housing.isApartment ? new ApartmentHousingStrategy(provider) : new HouseHousingStrategy(); return housing.isApartment ? new ApartmentHousingStrategy(calculator, decider) : new HouseHousingStrategy();
} }
} }

View File

@ -0,0 +1,19 @@
package pl.amu.edu.demo.logic;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
import pl.amu.edu.demo.providers.AverageQuoteProvider;
@RequiredArgsConstructor(staticName = "forProvider")
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@ToString
public class NetWorthCalculator {
AverageQuoteProvider provider;
public double calculateNetWorthForZipCode(String zipCode, double apartmentArea) {
return provider.getAverageQuotePerSquareMeter(zipCode) * apartmentArea;
}
}

View File

@ -0,0 +1,39 @@
package pl.amu.edu.demo.logic;
import lombok.RequiredArgsConstructor;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import static pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult.*;
public interface NetWorthDecider {
WeWillStealYourFlatCheckResult isEligible(double netWorth);
@RequiredArgsConstructor(staticName = "forThresholds")
class ThresholdPersonDecider implements NetWorthDecider {
final double ineligibilityThreshold;
final double backofficeDecisionThreshold;
@Override
public WeWillStealYourFlatCheckResult isEligible(double netWorth) {
if (netWorth < ineligibilityThreshold) {
return NOT_ELIGIBLE;
}
if (netWorth < backofficeDecisionThreshold) {
return BACKOFFICE_DECISION;
}
return ELIGIBLE;
}
}
static NetWorthDecider getInstance(boolean isPersonMarried) {
return isPersonMarried ?
new ThresholdPersonDecider(500000d, 550000d) :
new ThresholdPersonDecider(300000d, 350000d);
}
}

View File

@ -7,13 +7,21 @@ import pl.amu.edu.demo.providers.AverageQuoteProvider;
public class WeWillStealYourFlatProduct { public class WeWillStealYourFlatProduct {
private final AverageQuoteProvider provider; private final AverageQuoteProvider provider;
private final AgeDecider ageDecider;
public WeWillStealYourFlatProduct(AverageQuoteProvider provider) { public WeWillStealYourFlatProduct(AverageQuoteProvider provider) {
this.provider = provider; this.provider = provider;
this.ageDecider = new AgeDecider();
} }
public WeWillStealYourFlatCheckResult check(Person person) { public WeWillStealYourFlatCheckResult check(Person person) {
var strategy = HousingStrategy.getInstance(person.housing, provider); if (!ageDecider.isEligible(person.getAge())) {
return WeWillStealYourFlatCheckResult.NOT_ELIGIBLE;
}
var netWorthCalculator = NetWorthCalculator.forProvider(provider);
var netWorthDecider = NetWorthDecider.getInstance(person.isMarried);
var strategy = HousingStrategy.getInstance(person.housing, netWorthCalculator, netWorthDecider);
return strategy.checkEligibility(person.housing); return strategy.checkEligibility(person.housing);
} }

View File

@ -1,27 +0,0 @@
import org.junit.jupiter.api.Test;
import pl.amu.edu.demo.data.Person;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class TestStash {
@Test
public void shouldWhatever() {
// given
var person = new Person("lama", "frama", "lama", LocalDate.now(), null, false);
// when
var date = person.birthDate;
// then
// assertThat(date).isNotNull();
assertThrows(IllegalArgumentException.class, this::throwMeOut);
}
private void throwMeOut() {
throw new IllegalArgumentException();
}
}

View File

@ -0,0 +1,74 @@
package pl.amu.edu.demo.logic;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import pl.amu.edu.demo.data.Address;
import pl.amu.edu.demo.data.Housing;
import pl.amu.edu.demo.data.Person;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import java.time.LocalDate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult.*;
@SuppressWarnings("SpellCheckingInspection")
public class PersonAgeTestDataProvider implements ArgumentsProvider {
String[] names = {
"Miś Uszatek", "Gąska Balbinka", "Pies Pankracy", "Zając Poziomka", "Pepe LeSwąd"
};
LocalDate[] birthDates = {
LocalDate.now().minusYears(35).plusDays(1),
LocalDate.now().minusYears(35),
LocalDate.now().minusYears(44),
LocalDate.now().minusYears(65).plusDays(1),
LocalDate.now().minusYears(65),
};
WeWillStealYourFlatCheckResult[] eligibility = {
NOT_ELIGIBLE, ELIGIBLE, ELIGIBLE, ELIGIBLE, NOT_ELIGIBLE
};
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return IntStream
.range(0, names.length)
.mapToObj(this::buildPersonData)
.map(Arguments::of);
}
private PersonTestData buildPersonData(int index) {
return PersonTestData.of(buildPerson(index), eligibility[index]);
}
private Person buildPerson(int index) {
var displayName = names[index];
var nameSurname = displayName.split("\\s");
return Person.builder()
.displayName(displayName)
.firstName(nameSurname[0])
.lastName(nameSurname[1])
.isMarried(false)
.birthDate(birthDates[index])
.housing(buildHousing())
.build();
}
private Housing buildHousing() {
// This must always be worth millions
var address = Address.builder()
.withAddressLine1("Stumilowy Las")
.withAddressLine2("")
.withZipCode(TestZipCodes.PREMIUM_RATE_ZIP_CODE.getZipCode())
.build();
return Housing.builder()
.area(1234d)
.isApartment(true)
.address(address)
.build();
}
}

View File

@ -0,0 +1,70 @@
package pl.amu.edu.demo.logic;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import pl.amu.edu.demo.data.Address;
import pl.amu.edu.demo.data.Housing;
import pl.amu.edu.demo.data.Person;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import java.time.LocalDate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult.ELIGIBLE;
import static pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult.NOT_ELIGIBLE;
@SuppressWarnings("SpellCheckingInspection")
public class PersonApartmentTestDataProvider implements ArgumentsProvider {
String[] names = {
"Miś Uszatek", "Gąska Balbinka", "Pies Pankracy", "Zając Poziomka", "Pepe LeSwąd"
};
boolean[] apartments = {true, false, true, false, true};
WeWillStealYourFlatCheckResult[] eligibility = {
ELIGIBLE, NOT_ELIGIBLE, ELIGIBLE, NOT_ELIGIBLE, ELIGIBLE
};
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return IntStream
.range(0, names.length)
.mapToObj(this::buildPersonData)
.map(Arguments::of);
}
private PersonTestData buildPersonData(int index) {
return PersonTestData.of(buildPerson(index), eligibility[index]);
}
private Person buildPerson(int index) {
var displayName = names[index];
var nameSurname = displayName.split("\\s");
return Person.builder()
.displayName(displayName)
.firstName(nameSurname[0])
.lastName(nameSurname[1])
.isMarried(false)
.birthDate(LocalDate.now().minusYears(42))
.housing(buildHousing(index))
.build();
}
private Housing buildHousing(int index) {
// This must always be worth millions
var address = Address.builder()
.withAddressLine1("Stumilowy Las")
.withAddressLine2("")
.withZipCode(TestZipCodes.PREMIUM_RATE_ZIP_CODE.getZipCode())
.build();
// but it not always be an apartment
return Housing.builder()
.area(1234d)
.isApartment(apartments[index])
.address(address)
.build();
}
}

View File

@ -0,0 +1,70 @@
package pl.amu.edu.demo.logic;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import pl.amu.edu.demo.data.Address;
import pl.amu.edu.demo.data.Housing;
import pl.amu.edu.demo.data.Person;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import java.time.LocalDate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult.*;
@SuppressWarnings("SpellCheckingInspection")
public class PersonMarriedApartmentOwnersTestDataProvider implements ArgumentsProvider {
String[] names = {
"Miś Uszatek", "Gąska Balbinka", "Pies Pankracy", "Zając Poziomka"
};
double[] netWorth = {
499999.99d, 500000.00d, 549999.99d, 550000.00d };
WeWillStealYourFlatCheckResult[] eligibility = {
NOT_ELIGIBLE, BACKOFFICE_DECISION, BACKOFFICE_DECISION, ELIGIBLE
};
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return IntStream
.range(0, names.length)
.mapToObj(this::buildPersonData)
.map(Arguments::of);
}
private PersonTestData buildPersonData(int index) {
return PersonTestData.of(buildPerson(index), eligibility[index]);
}
private Person buildPerson(int index) {
var displayName = names[index];
var nameSurname = displayName.split("\\s");
return Person.builder()
.displayName(displayName)
.firstName(nameSurname[0])
.lastName(nameSurname[1])
.isMarried(true)
.birthDate(LocalDate.now().minusYears(42))
.housing(buildHousing(index))
.build();
}
private Housing buildHousing(int index) {
var ratePerSquareMeter = TestZipCodes.CHEAPEST_RATE_ZIP_CODE.getRatePerSquareMeters();
var address = Address.builder()
.withAddressLine1("Stumilowy Las")
.withAddressLine2("")
.withZipCode(TestZipCodes.CHEAPEST_RATE_ZIP_CODE.getZipCode())
.build();
// but it not always be an apartment
return Housing.builder()
.area(netWorth[index] / ratePerSquareMeter)
.isApartment(true)
.address(address)
.build();
}
}

View File

@ -0,0 +1,16 @@
package pl.amu.edu.demo.logic;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
import pl.amu.edu.demo.data.Person;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
@RequiredArgsConstructor(staticName = "of")
@FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true)
@ToString
public class PersonTestData {
Person person;
WeWillStealYourFlatCheckResult expectedResult;
}

View File

@ -0,0 +1,70 @@
package pl.amu.edu.demo.logic;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import pl.amu.edu.demo.data.Address;
import pl.amu.edu.demo.data.Housing;
import pl.amu.edu.demo.data.Person;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult;
import java.time.LocalDate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult.*;
@SuppressWarnings("SpellCheckingInspection")
public class PersonUnmarriedApartmentOwnersTestDataProvider implements ArgumentsProvider {
String[] names = {
"Miś Uszatek", "Gąska Balbinka", "Pies Pankracy", "Zając Poziomka"
};
double[] netWorth = {
299999.99d, 300000.00d, 349999.99d, 350000.00d };
WeWillStealYourFlatCheckResult[] eligibility = {
NOT_ELIGIBLE, BACKOFFICE_DECISION, BACKOFFICE_DECISION, ELIGIBLE
};
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return IntStream
.range(0, names.length)
.mapToObj(this::buildPersonData)
.map(Arguments::of);
}
private PersonTestData buildPersonData(int index) {
return PersonTestData.of(buildPerson(index), eligibility[index]);
}
private Person buildPerson(int index) {
var displayName = names[index];
var nameSurname = displayName.split("\\s");
return Person.builder()
.displayName(displayName)
.firstName(nameSurname[0])
.lastName(nameSurname[1])
.isMarried(false)
.birthDate(LocalDate.now().minusYears(42))
.housing(buildHousing(index))
.build();
}
private Housing buildHousing(int index) {
var ratePerSquareMeter = TestZipCodes.AVERAGE_RATE_ZIP_CODE.getRatePerSquareMeters();
var address = Address.builder()
.withAddressLine1("Stumilowy Las")
.withAddressLine2("")
.withZipCode(TestZipCodes.AVERAGE_RATE_ZIP_CODE.getZipCode())
.build();
// but it not always be an apartment
return Housing.builder()
.area(netWorth[index] / ratePerSquareMeter)
.isApartment(true)
.address(address)
.build();
}
}

View File

@ -1,75 +1,67 @@
package pl.amu.edu.demo.logic; package pl.amu.edu.demo.logic;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach;
import pl.amu.edu.demo.data.Address; import org.junit.jupiter.api.extension.ExtendWith;
import pl.amu.edu.demo.data.Housing; import org.junit.jupiter.params.ParameterizedTest;
import pl.amu.edu.demo.data.Person; import org.junit.jupiter.params.provider.ArgumentsSource;
import pl.amu.edu.demo.data.WeWillStealYourFlatCheckResult; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDate; import pl.amu.edu.demo.providers.AverageQuoteProvider;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TestWeWillStealYourFlatProduct { public class TestWeWillStealYourFlatProduct {
@Test @Mock
public void shouldBeValid() { AverageQuoteProvider provider;
// given
var person = Person.builder()
.displayName("Miś Uszatek")
.firstName("Miś")
.lastName("Uszatek")
.birthDate(LocalDate.of(1957, 3, 6))
.housing(
Housing.builder()
.address(
Address.builder()
.withAddressLine1("Stumilowy las 5")
.withZipCode("99-999")
.build()
).isApartment(true)
.area(36.0)
.build()
).isMarried(false)
.build();
var logic = new WeWillStealYourFlatProduct();
// when @BeforeEach
var result = logic.check(person); public void setUp() {
lenient().when(provider.getAverageQuotePerSquareMeter("98-765")).thenReturn(25000.0d);
// then
assertThat(result).isNotNull();
assertThat(result).isEqualTo(WeWillStealYourFlatCheckResult.ELIGIBLE);
} }
@Test @ParameterizedTest
public void shouldNotBeValid() { @ArgumentsSource(PersonAgeTestDataProvider.class)
public void shouldAcceptOnlyAgeRange(PersonTestData testData) {
commonTest(testData);
}
public void commonTest(PersonTestData testData) {
// given // given
var person = Person.builder() var person = testData.person;
.displayName("Maksymilian Paradys") var logic = new WeWillStealYourFlatProduct(provider);
.firstName("Maxymilian")
.lastName("Paradys")
.birthDate(LocalDate.of(1958, 11, 19))
.housing(
Housing.builder()
.address(
Address.builder()
.withAddressLine1("Blockhaus C")
.withZipCode("99-119")
.build()
).isApartment(false)
.area(6543.0)
.build()
).isMarried(true)
.build();
var logic = new WeWillStealYourFlatProduct();
// when // when
var result = logic.check(person); var result = logic.check(person);
// then // then
assertThat(result).isNotNull(); assertThat(result).isNotNull();
assertThat(result).isEqualTo(WeWillStealYourFlatCheckResult.NOT_ELIGIBLE); assertThat(result).isEqualTo(testData.expectedResult);
}
@ParameterizedTest
@ArgumentsSource(PersonApartmentTestDataProvider.class)
public void shouldAcceptApartmentsOnly(PersonTestData testData) {
commonTest(testData);
}
@ParameterizedTest
@ArgumentsSource(PersonUnmarriedApartmentOwnersTestDataProvider.class)
public void shouldDecideBasedOnNetWorthUnmarried(PersonTestData testData) {
when(provider.getAverageQuotePerSquareMeter(TestZipCodes.AVERAGE_RATE_ZIP_CODE.getZipCode()))
.thenReturn(TestZipCodes.AVERAGE_RATE_ZIP_CODE.getRatePerSquareMeters());
commonTest(testData);
}
@ParameterizedTest
@ArgumentsSource(PersonMarriedApartmentOwnersTestDataProvider.class)
public void shouldDecideBasedOnNetWorthMarried(PersonTestData testData) {
when(provider.getAverageQuotePerSquareMeter(TestZipCodes.CHEAPEST_RATE_ZIP_CODE.getZipCode()))
.thenReturn(TestZipCodes.CHEAPEST_RATE_ZIP_CODE.getRatePerSquareMeters());
commonTest(testData);
} }
} }

View File

@ -0,0 +1,21 @@
package pl.amu.edu.demo.logic;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@ToString
public enum TestZipCodes {
CHEAPEST_RATE_ZIP_CODE("55-464", 8000.0d),
AVERAGE_RATE_ZIP_CODE("77-646", 9500.0d),
PREMIUM_RATE_ZIP_CODE("98-765", 25000.0d);
String zipCode;
double ratePerSquareMeters;
}