From 1f33cdc90381b0afa12f643838ec301d680ebf9f Mon Sep 17 00:00:00 2001 From: Kai Moritz Date: Sat, 25 May 2024 10:17:57 +0200 Subject: [PATCH] top10: 1.1.2 - (RED) Explicitly formulated expectations for `Ranking` --- .../juplo/kafka/wordcount/top10/Ranking.java | 7 +- .../kafka/wordcount/top10/RankingTest.java | 230 ++++++++++++++++++ 2 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 src/test/java/de/juplo/kafka/wordcount/top10/RankingTest.java diff --git a/src/main/java/de/juplo/kafka/wordcount/top10/Ranking.java b/src/main/java/de/juplo/kafka/wordcount/top10/Ranking.java index 80e8742..0635384 100644 --- a/src/main/java/de/juplo/kafka/wordcount/top10/Ranking.java +++ b/src/main/java/de/juplo/kafka/wordcount/top10/Ranking.java @@ -12,6 +12,9 @@ import java.util.List; @Data public class Ranking { + public final static int MAX_ENTRIES = 10; + + private Entry[] entries = new Entry[0]; public Ranking add(Entry newEntry) @@ -41,9 +44,9 @@ public class Ranking break; } } - if (list.size() > 10) + if (list.size() > MAX_ENTRIES) { - list = list.subList(0,10); + list = list.subList(0, MAX_ENTRIES); } entries = list.toArray(num -> new Entry[num]); return this; diff --git a/src/test/java/de/juplo/kafka/wordcount/top10/RankingTest.java b/src/test/java/de/juplo/kafka/wordcount/top10/RankingTest.java new file mode 100644 index 0000000..e92d87c --- /dev/null +++ b/src/test/java/de/juplo/kafka/wordcount/top10/RankingTest.java @@ -0,0 +1,230 @@ +package de.juplo.kafka.wordcount.top10; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; + + +public class RankingTest +{ + @DisplayName("A newly created instance is empty") + @Test + public void testNewRankingIsEmpty() + { + Ranking ranking = new Ranking(); + assertThat(ranking.getEntries()).isEmpty(); + } + + @DisplayName("An instance that was build from an empty ranking is empty") + @Test + public void testRankingOfYieldsExpectedResultForEmptyList() + { + Ranking ranking = new Ranking(); + assertThat(ranking.getEntries()).isEmpty(); + } + + @DisplayName("An instance that was build from a valid ranking contains the expected entries") + @ParameterizedTest + @MethodSource("validRankingsProvider") + public void testRankingOfYieldsExpectedResultsForValidRankings(List entryList) + { + Ranking ranking = Ranking.of(toArray(entryList)); + assertThat(ranking.getEntries()).containsExactlyElementsOf(entryList); + } + + @DisplayName("The builder fails for invalid rankings") + @ParameterizedTest + @MethodSource("invalidRankingsProvider") + public void testRankingOfThrowsExceptionForInvalidRankings(List entryList) + { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> Ranking.of(toArray(entryList))); + } + + @DisplayName("Adding a new word with highest ranking, pushes all other words down") + @ParameterizedTest + @MethodSource("validRankingsProvider") + public void testAddingNewWordWithHighestRanking(List entryList) + { + Ranking ranking = Ranking.of(toArray(entryList)); + Entry newEntry = Entry.of("NEW!", rankingForPosition(-1)); + ranking.add(newEntry); + assertThat(ranking.getEntries()[0]).isEqualTo(newEntry); + for (int i = 0; i < entryList.size() && i < Ranking.MAX_ENTRIES - 1; i++) + { + assertThat(ranking.getEntries()[i + 1]).isEqualTo(entryList.get(i)); + } + } + + @DisplayName("Adding a new word with an existent ranking, pushes all words with lower ranking down") + @ParameterizedTest + @MethodSource("validRankingsProvider") + public void testAddingNewWordWithExistingRanking(List entryList) + { + for (int position = 0; position < entryList.size(); position++ ) + { + Ranking ranking = Ranking.of(toArray(entryList)); + Entry newEntry = Entry.of("NEW!", rankingForPosition(position)); + ranking.add(newEntry); + for (int i = 0; i < entryList.size() && i < Ranking.MAX_ENTRIES - 1; i++) + { + if (i < position) + { + assertThat(ranking.getEntries()[i]).isEqualTo(entryList.get(i)); + } + if (i == position) + { + assertThat(ranking.getEntries()[i]).isEqualTo(entryList.get(i)); + assertThat(ranking.getEntries()[i + 1]).isEqualTo(newEntry); + } + if (i > position) + { + assertThat(ranking.getEntries()[i + 1]).isEqualTo(entryList.get(i)); + } + } + } + } + + @DisplayName("Adding a highest ranking for an existing word shifts it to the first place") + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) + public void testAddingExistingWordWithHighestRanking(int position) + { + Ranking ranking = Ranking.of(toArray(VALID_RANKINGS[0])); + String word = wordForPosition(position); + Entry highestEntry = Entry.of(word, 100l); + ranking.add(highestEntry); + List expectedEntries = Stream + .concat( + Stream.of(highestEntry), + VALID_RANKINGS[0] + .stream() + .filter(entry -> !entry.getWord().equals(word))) + .toList(); + assertThat(ranking.getEntries()).containsExactlyElementsOf(expectedEntries); + } + + @DisplayName("Adding an existing word with unchanged ranking changes nothing") + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) + public void testAddingExistingWordWithUnchangedRanking(int position) + { + Ranking ranking = Ranking.of(toArray(VALID_RANKINGS[0])); + Entry unchangedEntry = Entry.of( + wordForPosition(position), + rankingForPosition(position)); + ranking.add(unchangedEntry); + assertThat(ranking.getEntries()).containsExactlyElementsOf(VALID_RANKINGS[0]); + } + + @DisplayName("Adding an existing word with a lower ranking fails") + @ParameterizedTest + @MethodSource("validRankingsProvider") + public void testAddingExistingWordWithLowerRankingFails(List entryList) + { + Ranking ranking = Ranking.of(toArray(entryList)); + entryList.forEach(entry -> + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ranking.add(Entry.of(entry.getWord(), entry.getCounter() - 1)))); + } + + + Entry[] toArray(List entryList) + { + return entryList.toArray(size -> new Entry[size]); + } + + static String wordForPosition(int position) + { + return Integer.toString(position+1); + } + + static long rankingForPosition(int position) + { + return (long)Ranking.MAX_ENTRIES * 2 - position; + } + + static Stream> validRankingsProvider() + { + return Stream.of(VALID_RANKINGS); + } + + static Stream> invalidRankingsProvider() + { + return Stream.of(INVALID_RANKINGS); + } + + static String[] WORDS = new String[Ranking.MAX_ENTRIES]; + static List[] VALID_RANKINGS = new List[Ranking.MAX_ENTRIES]; + + static + { + for (int i = 0; i < Ranking.MAX_ENTRIES; i++) + { + List ranking = new LinkedList<>(); + String word = null; + for (int position = 0; position <= i; position++) + { + word = wordForPosition(position); + Entry entry = Entry.of(word, rankingForPosition(position)); + ranking.add(entry); + } + WORDS[i] = word; + VALID_RANKINGS[Ranking.MAX_ENTRIES - (i + 1)] = ranking; + } + } + + static List[] INVALID_RANKINGS = new List[] { + List.of( + Entry.of("Platz eins", 1l), + Entry.of("Platz zwei", 2l)), + List.of( + Entry.of("Platz eins", 1111111111l), + Entry.of("Platz zwei", 222222222l), + Entry.of("Platz eins", 1l)), + List.of( + Entry.of("Platz eins", 11l), + Entry.of("Platz eins", 1l)), + List.of( + Entry.of("Platz eins", 1111111111l), + Entry.of("Platz zwei", 222222222l), + Entry.of("Platz eins", 11111111l), + Entry.of("Platz zwei", 2222222l), + Entry.of("Platz fünf", 555555l)), + List.of( + Entry.of("Platz eins", 1111111111l), + Entry.of("Platz zwei", 222222222l), + Entry.of("Platz drei", 33333333l), + Entry.of("Platz vier", 4444444l), + Entry.of("Platz eins", 111111l), + Entry.of("Platz sechs", 66666l)), + List.of( + Entry.of("Platz eins", 1111111111l), + Entry.of("Platz zwei", 222222222l), + Entry.of("Platz drei", 33333333l), + Entry.of("Platz vier", 4444444l), + Entry.of("Platz fünf", 555555l), + Entry.of("Platz sechs", 66666l), + Entry.of("Platz eins", 1l)), + List.of( + Entry.of("Platz eins", 1111111111l), + Entry.of("Platz zwei", 222222222l), + Entry.of("Platz drei", 33333333l), + Entry.of("Platz vier", 4444444l), + Entry.of("Platz fünf", 555555l), + Entry.of("Platz sechs", 66666l), + Entry.of("Platz sieben", 7777l), + Entry.of("Platz acht", 888l), + Entry.of("Platz neun", 99l), + Entry.of("Platz 10", 6l), + Entry.of("Platz 11", 3l))}; +} -- 2.20.1