Skip to content

Commit 40d7af0

Browse files
committed
KTLN-596: property based testing
1 parent d0cbb91 commit 40d7af0

File tree

5 files changed

+271
-1
lines changed

5 files changed

+271
-1
lines changed

core-kotlin-modules/core-kotlin-strings-5/pom.xml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,26 @@
2222
</dependency>
2323
</dependencies>
2424

25+
<build>
26+
<plugins>
27+
<plugin>
28+
<groupId>org.jetbrains.kotlin</groupId>
29+
<artifactId>kotlin-maven-plugin</artifactId>
30+
<version>${kotlin.version}</version>
31+
32+
<configuration>
33+
<args>
34+
<arg>-java-parameters</arg> <!-- Get correct parameter names in jqwik reporting -->
35+
<arg>-Xjsr305=strict</arg> <!-- Strict interpretation of nullability annotations in jqwik API -->
36+
<arg>-Xemit-jvm-type-annotations</arg> <!-- Enable annotations on type variables -->
37+
</args>
38+
</configuration>
39+
</plugin>
40+
</plugins>
41+
</build>
42+
2543
<properties>
2644
<junit-jupiter-params.version>5.10.2</junit-jupiter-params.version>
2745
</properties>
2846

29-
</project>
47+
</project>

kotlin-testing/pom.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<version>${spek.version}</version>
3333
<scope>test</scope>
3434
</dependency>
35+
3536
<dependency>
3637
<groupId>ch.tutteli.atrium</groupId>
3738
<artifactId>atrium-fluent-jvm</artifactId>
@@ -45,10 +46,49 @@
4546
<scope>test</scope>
4647
</dependency>
4748

49+
<dependency>
50+
<groupId>net.jqwik</groupId>
51+
<artifactId>jqwik</artifactId>
52+
<version>${jqwik.version}</version>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>net.jqwik</groupId>
57+
<artifactId>jqwik-kotlin</artifactId>
58+
<version>${jqwik.version}</version>
59+
<scope>test</scope>
60+
</dependency>
61+
62+
<dependency>
63+
<groupId>org.junit.jupiter</groupId>
64+
<artifactId>junit-jupiter-params</artifactId>
65+
<version>${junit-jupiter-params.version}</version>
66+
<scope>test</scope>
67+
</dependency>
4868
</dependencies>
4969

70+
<build>
71+
<plugins>
72+
<plugin>
73+
<groupId>org.jetbrains.kotlin</groupId>
74+
<artifactId>kotlin-maven-plugin</artifactId>
75+
<version>${kotlin.version}</version>
76+
<configuration>
77+
<args>
78+
<arg>-java-parameters</arg> <!-- Get correct parameter names in jqwik reporting -->
79+
<arg>-Xjsr305=strict
80+
</arg> <!-- Strict interpretation of nullability annotations in jqwik API -->
81+
<arg>-Xemit-jvm-type-annotations</arg> <!-- Enable annotations on type variables -->
82+
</args>
83+
</configuration>
84+
</plugin>
85+
</plugins>
86+
</build>
87+
5088
<properties>
5189
<spek.version>1.1.5</spek.version>
5290
<atrium.version>1.2.0</atrium.version>
91+
<jqwik.version>1.8.5</jqwik.version>
92+
<junit-jupiter-params.version>5.9.1</junit-jupiter-params.version>
5393
</properties>
5494
</project>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.baeldung.propertytesting
2+
3+
import org.junit.jupiter.params.ParameterizedTest
4+
import org.junit.jupiter.params.provider.Arguments
5+
import org.junit.jupiter.params.provider.MethodSource
6+
import kotlin.test.assertEquals
7+
8+
class JUnit5_ExampleVsPeopertyBasedUnitTest {
9+
10+
val converter = RomanNumeralsConverter()
11+
12+
companion object {
13+
@JvmStatic
14+
fun intsToRomanNumerals() = listOf(
15+
Arguments.of(3, "III"),
16+
Arguments.of(4, "IV"),
17+
Arguments.of(58, "LVIII"),
18+
Arguments.of(1234 , "MCCXXXIV")
19+
);
20+
21+
@JvmStatic
22+
fun randomIntsInRangeInRangeZeroTo4k() = (0..<4000).shuffled().take(300)
23+
}
24+
25+
@ParameterizedTest
26+
@MethodSource("intsToRomanNumerals")
27+
fun `should converted integer to roman numeral (example-based test)`(integer: Int, roman: String) {
28+
assertEquals(roman, converter.intToRoman(integer))
29+
}
30+
31+
@ParameterizedTest
32+
@MethodSource("intsToRomanNumerals")
33+
fun `should converted roman numeral to integer (example-based test)`(integer: Int, roman: String) {
34+
assertEquals(integer, converter.romanToInt(roman))
35+
}
36+
37+
@ParameterizedTest
38+
@MethodSource("randomIntsInRangeInRangeZeroTo4k")
39+
fun `should converted integer to roman numeral and back (property-based test) `(integer: Int) {
40+
val roman = converter.intToRoman(integer)
41+
assertEquals(integer, converter.romanToInt(roman))
42+
}
43+
44+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.baeldung.propertytesting
2+
3+
import net.jqwik.api.*
4+
import net.jqwik.api.constraints.*
5+
import net.jqwik.api.constraints.CharRange
6+
import net.jqwik.api.constraints.IntRange
7+
import net.jqwik.kotlin.api.any
8+
import net.jqwik.kotlin.api.combine
9+
import net.jqwik.kotlin.api.ofLength
10+
import net.jqwik.time.api.Dates
11+
import java.time.LocalDate
12+
import java.time.Month
13+
import kotlin.test.assertTrue
14+
15+
class Jqwik_PropertyBasedUnitTest {
16+
17+
val converter = RomanNumeralsConverter()
18+
19+
@Property
20+
fun `should generate input values`(@ForAll arg: Int) {
21+
println("$arg")
22+
assertTrue { arg is Int }
23+
}
24+
25+
@Property(tries = 100, seed = "-1329617974163688825")
26+
fun `should generate input values for a given seed`(@ForAll arg: Int): Boolean {
27+
println("$arg")
28+
return arg is Int
29+
}
30+
31+
@Property
32+
fun `should convert integer to roman and back`(
33+
@ForAll @IntRange(min = 0, max = 3999) originalInt: Int,
34+
): Boolean {
35+
val roman = converter.intToRoman(originalInt)
36+
val finalInt = converter.romanToInt(roman)
37+
return originalInt == finalInt
38+
}
39+
40+
41+
@Property(tries = 4000)
42+
fun `should convert integer to roman and back for all valid inputs (exhaustive testing)`(
43+
@ForAll @IntRange(min = 0, max = 3999) originalInt: Int,
44+
): Boolean {
45+
val roman = converter.intToRoman(originalInt)
46+
val finalInt = converter.romanToInt(roman)
47+
return originalInt == finalInt
48+
}
49+
50+
@Report(Reporting.GENERATED)
51+
@Property(tries = 10)
52+
fun `should generate Strings`(
53+
@ForAll @AlphaChars @UniqueChars @NotBlank foo: String,
54+
@ForAll @Whitespace @CharRange(from = '!', to = '&') @StringLength(min = 3, max = 5) bar: String,
55+
): Boolean {
56+
val fooBar = "$foo $bar"
57+
return fooBar.length == foo.length + bar.length + 1
58+
}
59+
60+
@Property(tries = 10)
61+
fun `should generate Lists of Doubles`(
62+
@ForAll @NotEmpty @Size(max = 10) numbers: @UniqueElements List<Double>,
63+
): Boolean {
64+
return numbers.size > 0
65+
}
66+
67+
@Property
68+
fun `should generate Enums and Booleans`(
69+
@ForAll month: Month,
70+
@ForAll isLeapYear: Boolean?,
71+
@ForAll @IntRange(min = 2000, max = 2024) year: Int,
72+
): Boolean {
73+
val display = "$year $month ${isLeapYear ?: "*"}"
74+
return display.isNotEmpty()
75+
}
76+
77+
@Property
78+
fun `should be able to sign up`(
79+
@ForAll @AlphaChars @NumericChars @UniqueChars @StringLength(min = 3, max = 8) username: String,
80+
): Boolean {
81+
return username.length >= 3
82+
}
83+
84+
@Property
85+
fun `should be able to login`(@ForAll("usernames") username: String): Boolean {
86+
return username.length >= 3
87+
}
88+
89+
@Property
90+
fun `should validate account`(@ForAll("accounts") account: Account): Boolean {
91+
return account.username.length >= 3
92+
}
93+
94+
@Provide
95+
fun usernames(): Arbitrary<String> = String.any()
96+
.alpha()
97+
.numeric()
98+
.uniqueChars()
99+
.ofLength(3..8)
100+
101+
data class Account(val id: Long, val username: String, val dateOfBirth: LocalDate)
102+
103+
@Provide
104+
fun accounts(): Arbitrary<Account> {
105+
val ids = Long.any().greaterOrEqual(100)
106+
val datesOfBirth = Dates.dates().yearBetween(1900, 2000)
107+
return combine(ids, usernames(), datesOfBirth, combinator = ::Account)
108+
}
109+
110+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.baeldung.propertytesting
2+
3+
import org.assertj.core.api.Assertions
4+
import org.junit.jupiter.api.Test
5+
import java.util.*
6+
7+
class RomanNumeralsConverter {
8+
9+
private val romanPairs = mapOf(
10+
"M" to 1000,
11+
"CM" to 900,
12+
"D" to 500,
13+
"CD" to 400,
14+
"C" to 100,
15+
"XC" to 90,
16+
"L" to 50,
17+
"XL" to 40,
18+
"X" to 10,
19+
"IX" to 9,
20+
"V" to 5,
21+
"IV" to 4,
22+
"I" to 1
23+
)
24+
25+
fun intToRoman(num: Int): String {
26+
if (num < 0 || num > 3999)
27+
throw IllegalArgumentException("the argument has to be in [0, 4_000) range.")
28+
29+
var value = num
30+
val roman = StringBuilder()
31+
32+
for (pair in romanPairs) {
33+
while (value >= pair.value) {
34+
value -= pair.value
35+
roman.append(pair.key)
36+
}
37+
}
38+
return roman.toString()
39+
}
40+
41+
fun romanToInt(roman: String): Int {
42+
var result = 0
43+
var i = 0
44+
while (i < roman.length) {
45+
if (i + 1 < roman.length && romanPairs.containsKey(roman.substring(i, i + 2))) {
46+
result += romanPairs[roman.substring(i, i + 2)]!!
47+
i += 2
48+
} else {
49+
result += romanPairs[roman.substring(i, i + 1)]!!
50+
i++
51+
}
52+
}
53+
return result
54+
}
55+
56+
57+
58+
}

0 commit comments

Comments
 (0)