diff --git a/core/build.gradle b/core/build.gradle index 28551f0..09bfc2d 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,5 +1,7 @@ dependencies { testImplementation dependency.junit_jupiter_api + testCompileOnly dependency.lombok + testAnnotationProcessor dependency.lombok testRuntimeOnly dependency.junit_jupiter_engine } diff --git a/core/src/main/java/com/tngtech/valueprovider/ValueProvider.java b/core/src/main/java/com/tngtech/valueprovider/ValueProvider.java index 51187c4..7a49ead 100644 --- a/core/src/main/java/com/tngtech/valueprovider/ValueProvider.java +++ b/core/src/main/java/com/tngtech/valueprovider/ValueProvider.java @@ -20,6 +20,7 @@ import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; import com.google.common.collect.Iterables; @@ -34,6 +35,7 @@ import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.ChronoUnit.SECONDS; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; @SuppressWarnings("WeakerAccess") public class ValueProvider { @@ -829,6 +831,136 @@ public final T oneOfExcluding(Iterable elements, T... elementsToExclude) return oneOf(allElements); } + /** + * Generates a {@link List} of <T> (by means of {@code generator}) and includes {@code furtherContainedElements} in the {@link List}. + *

+ * Example: + *

+     *          static class MyBeanTestData {
+     *              public static MyBean myBean(ValueProvider valueProvider) {
+     *                  // builds and returns your bean
+     *              }
+     *          }
+     *
+     *         ValueProvider vp = ValueProviderFactory.createRandomValueProvider();
+     *         vp.listOfContaining(MyBeanTestData::myBean, myBean(), myBean(), myBean()); // -> List[myBean_1, myBean_2, myBean_random, myBean_3, myBean_random]
+     * 
+ *

+ * + * @param generator a generator {@link Function} to generate T given a {@link ValueProvider}. + * @param firstContainedElement first element that should be contained in the generated list. + * @param furtherContainedElements further elements that should be contained in the generated list. + * + * @return the generated {@link List}. + * + * @see #listOf(Function) + */ + @SafeVarargs + public final List listOfContaining(Function generator, T firstContainedElement, T... furtherContainedElements) { + return listOfContaining(generator, asList(firstContainedElement, furtherContainedElements)); + } + + /** + * see {@link #listOfContaining(Function, Object, Object[])} + */ + public final List listOfContaining(Function generator, Collection containedElements) { + int maxNumberOfRandomElements = maxNumberOfRandomElements(containedElements); + + List generatedElements = new ArrayList<>(); + for (T containedValue : containedElements) { + generatedElements.addAll(listOf(generator, intNumber(0, maxNumberOfRandomElements))); + generatedElements.add(containedValue); + } + generatedElements.addAll(listOf(generator, intNumber(0, maxNumberOfRandomElements))); + return generatedElements; + } + + private int maxNumberOfRandomElements(Collection containedElements) { + if (containedElements.size() == 0) { + return 5; + } + if (containedElements.size() == 1) { + return 2; + } + return 1; + } + + /** + * Generates a {@link List} of <T> (by means of {@code generator}). Ensures that the {@link List} contains at least one element. + *

+ * Example: + *

+     *          static class MyBeanTestData {
+     *              public static MyBean myBean(ValueProvider valueProvider) {
+     *                  // builds and returns your bean
+     *              }
+     *          }
+     *
+     *         ValueProvider vp = ValueProviderFactory.createRandomValueProvider();
+     *         vp.nonEmptyListOf(MyBeanTestData::myBean); // -> List[myBean_generated_1, myBean_generated_2]
+     * 
+ *

+ * + * @param generator a generator {@link Function} to generate T given a {@link ValueProvider}. + * + * @return the generated {@link List}. + */ + public List nonEmptyListOf(Function generator) { + return listOf(generator, intNumber(1, 5)); + } + + /** + * Generates a {@link List} of <T> (by means of {@code generator}). Might return the empty list. + *

+ * Example: + *

+     *          static class MyBeanTestData {
+     *              public static MyBean myBean(ValueProvider valueProvider) {
+     *                  // builds and returns your bean
+     *              }
+     *          }
+     *
+     *         ValueProvider vp = ValueProviderFactory.createRandomValueProvider();
+     *         vp.listOf(MyBeanTestData::myBean); // -> List[myBean_generated_1, myBean_generated_2]
+     *         vp.listOf(MyBeanTestData::myBean); // -> List[]
+     * 
+ *

+ * + * @param generator a generator {@link Function} to generate T given a {@link ValueProvider}. + * + * @return the generated {@link List}. + */ + public List listOf(Function generator) { + return listOf(generator, intNumber(0, 5)); + } + + /** + * Generates a {@link List} of <T> (by means of {@code generator}). Containing exactly {@code numberOfElements} elements. + *

+ * Example: + *

+     *          static class MyBeanTestData {
+     *              public static MyBean myBean(ValueProvider valueProvider) {
+     *                  // builds and returns your bean
+     *              }
+     *          }
+     *
+     *         ValueProvider vp = ValueProviderFactory.createRandomValueProvider();
+     *         vp.listOf(MyBeanTestData::myBean, 4); // -> List[myBean_generated_1, myBean_generated_2, myBean_generated_3, myBean_generated_4]
+     * 
+ *

+ * + * @param generator a generator {@link Function} to generate T given a {@link ValueProvider}. + * + * @return the generated {@link List}. + */ + public List listOf(Function generator, int numberOfElements) { + //noinspection unchecked + return IntStream.range(0, numberOfElements) + .mapToObj(i -> generator.apply((V) this)) + .collect(toList()); + } + /** * Randomly draws true or false. * @@ -907,7 +1039,7 @@ private String createIPv6Block() { } private List truncateLeadingZerosFromBlocks(List blocks) { - return blocks.stream().map(this::truncateLeadingZerosFromBlock).collect(Collectors.toList()); + return blocks.stream().map(this::truncateLeadingZerosFromBlock).collect(toList()); } private String truncateLeadingZerosFromBlock(String block) { diff --git a/core/src/test/java/com/tngtech/valueprovider/ValueProviderTest.java b/core/src/test/java/com/tngtech/valueprovider/ValueProviderTest.java index e45ac7b..c709d17 100644 --- a/core/src/test/java/com/tngtech/valueprovider/ValueProviderTest.java +++ b/core/src/test/java/com/tngtech/valueprovider/ValueProviderTest.java @@ -20,6 +20,7 @@ import java.util.stream.LongStream; import java.util.stream.Stream; +import lombok.Data; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; @@ -32,6 +33,7 @@ import static com.tngtech.valueprovider.ValueProviderTest.MethodInvocation.assertDifferentResultAsFarAsPossible; import static com.tngtech.valueprovider.ValueProviderTest.MethodInvocation.assertEqualResult; import static com.tngtech.valueprovider.ValueProviderTest.MethodInvocation.invoke; +import static com.tngtech.valueprovider.ValueProviderTest.MyBeanTestData.myBeanContained; import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.EIGHT; import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.ELEVEN; import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.FIVE; @@ -42,7 +44,7 @@ import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.SIX; import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.TEN; import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.THREE; -import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.TWELFE; +import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.TWELVE; import static com.tngtech.valueprovider.ValueProviderTest.TestEnum.TWO; import static java.time.LocalDateTime.now; import static java.util.Arrays.stream; @@ -218,6 +220,17 @@ void intNumber_should_create_correct_numbers_for_the_limits_of_the_integer_range assertIntNumber(Integer.MAX_VALUE, Integer.MAX_VALUE); } + private void assertIntNumber(int min, int max) { + assertIntNumber(withRandomValues(), min, max); + assertIntNumber(withFixedValues(), min, max); + } + + private void assertIntNumber(ValueProvider provider, int min, int max) { + assertThat(provider.intNumber(min, max)) + .isGreaterThanOrEqualTo(min) + .isLessThanOrEqualTo(max); + } + @Test void longNumber_should_create_number_between_min_and_max_and_outside_integer_range() { for (long i = 0; i < 1000; i++) { @@ -243,6 +256,18 @@ void longNumber_should_create_correct_numbers_for_the_limits_of_the_long_range() assertLongNumber(Long.MAX_VALUE, Long.MAX_VALUE); } + private void assertLongNumber(long min, long max) { + assertLongNumber(withRandomValues(), min, max); + assertLongNumber(withFixedValues(), min, max); + } + + private void assertLongNumber(ValueProvider provider, long min, long max) { + long longNumber = provider.longNumber(min, max); + assertThat(longNumber) + .isGreaterThanOrEqualTo(min) + .isLessThanOrEqualTo(max); + } + @Test void longNumber_should_create_random_numbers_for_the_entire_long_range() { long min = 0L; @@ -290,6 +315,16 @@ void bigIntegerNumber_should_create_number_between_min_and_max_and_exceeding_lon } } + private void assertBigIntegerNumber(BigInteger min, BigInteger max) { + assertBigIntegerNumber(withRandomValues(), min, max); + assertBigIntegerNumber(withFixedValues(), min, max); + } + + private void assertBigIntegerNumber(ValueProvider provider, BigInteger min, BigInteger max) { + assertThat(provider.bigIntegerNumber(min, max)) + .isBetween(min, max); + } + @SuppressWarnings("unchecked") @Test void integer_number_methods_should_handle_range_zero_to_one_properly() { @@ -386,9 +421,27 @@ void bigDecimalNumber_should_create_number_between_min_and_max() { } } + private void assertBigDecimalNumber(double min, double max) { + assertBigDecimalNumber(withRandomValues(), min, max); + assertBigDecimalNumber(withFixedValues(), min, max); + } + + private void assertBigDecimalNumber(ValueProvider provider, double min, double max) { + assertThat(provider.bigDecimalNumber(min, max)) + .isBetween(BigDecimal.valueOf(min), BigDecimal.valueOf(max)); + } + @Test void bigDecimalNumberWithScale_should_return_numbers_within_specified_range_as_long_as_scale_allows_it() { - assertBigDecimalNumberWithScale2(1.001, 1.004); + double min = 1.001; + double max = 1.004; + int scale = 2; + double offsetForScale = 0.01; + + assertThat(withRandomValues().bigDecimalNumberWithScale(min, max, scale)) + .isBetween(BigDecimal.valueOf(min - offsetForScale), BigDecimal.valueOf(max + offsetForScale)); + assertThat(withFixedValues().bigDecimalNumberWithScale(min, max, scale)) + .isBetween(BigDecimal.valueOf(min - offsetForScale), BigDecimal.valueOf(max + offsetForScale)); } @TestFactory @@ -396,7 +449,7 @@ List numericString_should_allow_restricting_min_and_max_in_addition return newArrayList( lengthMinMax("unrestricted", 3, 100, 200), lengthMinMax("restricted to range", 3, 120, 180), - lengthMinMax("restricted to single value", 3, 150, 150) + lengthMinMax("restricted to single value", 4, 1234, 1234) ); } @@ -404,8 +457,7 @@ private static DynamicTest lengthMinMax(String name, int length, int min, int ma return DynamicTest.dynamicTest(name, () -> { String numericString = withRandomValues().numericString(length, min, max); - assertThat(Integer.valueOf(numericString)).isGreaterThanOrEqualTo(min); - assertThat(Integer.valueOf(numericString)).isLessThanOrEqualTo(max); + assertThat(Integer.valueOf(numericString)).isBetween(min, max); }); } @@ -481,7 +533,7 @@ void oneOfExcluding_should_return_allowed_values_only() { assertThat(random.oneOfExcluding(newArrayList("1", "2", "3", "4"), "3", "4")).isIn("1", "2").isNotIn("3", "4"); assertThat(random.oneOfExcluding(ONE, THREE, FIVE, SEVEN, NINE, ELEVEN)) - .isIn(TWO, FOUR, SIX, EIGHT, TEN, TWELFE) + .isIn(TWO, FOUR, SIX, EIGHT, TEN, TWELVE) .isNotIn(ONE, THREE, FIVE, SEVEN, NINE, ELEVEN); } @@ -570,7 +622,59 @@ void someOf_should_return_duplicates_if_input_contains_duplicates() { assertThat(random.someOf(valueList, valueList.size())) .isSubsetOf(valueSet) - .hasSize(valueList.size()); + .hasSameSizeAs(valueList); + } + + @Test + void listOf_should_return_provided_number_of_elements() { + // given + ValueProvider random = withRandomValues(); + int numberOfElements = random.intNumber(5, 10); + + // when + List myBeans = random.listOf(MyBeanTestData::myBean, numberOfElements); + + // then + assertThat(myBeans).hasSize(numberOfElements); + } + + @Test + void listOf_should_return_a_sensible_number_of_elements() { + // given + ValueProvider random = withRandomValues(); + + // when + List myBeans = random.listOf(MyBeanTestData::myBean); + + // then + assertThat(myBeans).hasSizeLessThanOrEqualTo(5); // 5 is the default + } + + @Test + void nonEmptyListOf_should_return_at_least_one_element() { + // given + ValueProvider random = withRandomValues(); + + // when + List myBeans = random.nonEmptyListOf(MyBeanTestData::myBean); + + // then + assertThat(myBeans).isNotEmpty() + .hasSizeLessThanOrEqualTo(5); // 5 is the default + } + + @Test + void listOfContaining_should_return_the_provided_elements_plus_some_randomly_generated_elements() { + // given + ValueProvider random = withRandomValues(); + + // when + List myBeans = random.listOfContaining(MyBeanTestData::myBean, myBeanContained(1), myBeanContained(2), myBeanContained(3)); + + // then + assertThat(myBeans).hasSizeLessThanOrEqualTo(7) + // 3 contained beans + max. 1 random 'spacing' bean between each + max. 1 random bean at the beginning/end + .contains(myBeanContained(1), myBeanContained(2), myBeanContained(3)); } @Test @@ -603,74 +707,28 @@ private static ValueProvider withRandomValues() { } private ValueProvider withFixedValues() { - return withFixedValues(0L); - } - - private ValueProvider withFixedValues(long seed) { - return new ValueProvider(createReproducibleInitialization(seed)); + return new ValueProvider(createReproducibleInitialization(0L)); } enum TestEnum { - ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELFE + ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE } enum EmptyTestEnum { - - } - - private void assertIntNumber(int min, int max) { - assertIntNumber(withRandomValues(), min, max); - assertIntNumber(withFixedValues(), min, max); - } - - private void assertIntNumber(ValueProvider provider, int min, int max) { - assertThat(provider.intNumber(min, max)) - .isGreaterThanOrEqualTo(min) - .isLessThanOrEqualTo(max); - } - - private void assertLongNumber(long min, long max) { - assertLongNumber(withRandomValues(), min, max); - assertLongNumber(withFixedValues(), min, max); - } - - private void assertLongNumber(ValueProvider provider, long min, long max) { - long longNumber = provider.longNumber(min, max); - assertThat(longNumber) - .isGreaterThanOrEqualTo(min) - .isLessThanOrEqualTo(max); } - private void assertBigIntegerNumber(BigInteger min, BigInteger max) { - assertBigIntegerNumber(withRandomValues(), min, max); - assertBigIntegerNumber(withFixedValues(), min, max); - } - - private void assertBigIntegerNumber(ValueProvider provider, BigInteger min, BigInteger max) { - assertThat(provider.bigIntegerNumber(min, max)) - .isGreaterThanOrEqualTo(min) - .isLessThanOrEqualTo(max); - } - - private void assertBigDecimalNumber(Number min, Number max) { - assertBigDecimalNumber(withRandomValues(), min, max); - assertBigDecimalNumber(withFixedValues(), min, max); - } - - private void assertBigDecimalNumber(ValueProvider provider, Number min, Number max) { - assertThat(provider.bigDecimalNumber(min, max)) - .isGreaterThanOrEqualTo(new BigDecimal(min.doubleValue())) - .isLessThanOrEqualTo(new BigDecimal(max.doubleValue())); - } + static class MyBeanTestData { + public static MyBean myBean(ValueProvider valueProvider) { + return new MyBean("randomly generated" + valueProvider.intNumber(0, Integer.MAX_VALUE)); + } - private void assertBigDecimalNumberWithScale2(Number min, Number max) { - assertBigDecimalNumberWithScale2(withRandomValues(), min, max); - assertBigDecimalNumberWithScale2(withFixedValues(), min, max); + public static MyBean myBeanContained(int counter) { + return new MyBean("contained" + counter); + } } - private void assertBigDecimalNumberWithScale2(ValueProvider provider, Number min, Number max) { - assertThat(provider.bigDecimalNumberWithScale(min, max, 2)) - .isGreaterThanOrEqualTo(new BigDecimal(min.doubleValue() - 0.01)) - .isLessThanOrEqualTo(new BigDecimal(max.doubleValue() + 0.01)); + @Data + static class MyBean { + private final String value; } }