Skip to content

Commit 670198e

Browse files
authored
Merge pull request #985 from etrandafir93/features/KTLN-596_jqwik_property_testing
KTLN-596: jqwik property testing
2 parents 8f99bc4 + 1cb58c9 commit 670198e

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed

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_ExampleVsPropertyBasedUnitTest {
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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
@Property(tries = 4000)
41+
fun `should convert integer to roman and back for all valid inputs (exhaustive testing)`(
42+
@ForAll @IntRange(min = 0, max = 3999) originalInt: Int,
43+
): Boolean {
44+
val roman = converter.intToRoman(originalInt)
45+
val finalInt = converter.romanToInt(roman)
46+
return originalInt == finalInt
47+
}
48+
49+
@Report(Reporting.GENERATED)
50+
@Property(tries = 10)
51+
fun `should generate Strings`(
52+
@ForAll @AlphaChars @UniqueChars @NotBlank foo: String,
53+
@ForAll @Whitespace @CharRange(from = '!', to = '&') @StringLength(min = 3, max = 5) bar: String,
54+
): Boolean {
55+
val fooBar = "$foo $bar"
56+
return fooBar.length == foo.length + bar.length + 1
57+
}
58+
59+
@Property(tries = 10)
60+
fun `should generate Lists of Doubles`(
61+
@ForAll @NotEmpty @Size(max = 10) numbers: @UniqueElements List<Double>,
62+
): Boolean {
63+
return numbers.size > 0
64+
}
65+
66+
@Property
67+
fun `should generate Enums and Booleans`(
68+
@ForAll month: Month,
69+
@ForAll isLeapYear: Boolean?,
70+
@ForAll @IntRange(min = 2000, max = 2024) year: Int,
71+
): Boolean {
72+
val display = "$year $month ${isLeapYear ?: "*"}"
73+
return display.isNotEmpty()
74+
}
75+
76+
@Property
77+
fun `should be able to sign up`(
78+
@ForAll @AlphaChars @NumericChars @UniqueChars @StringLength(min = 3, max = 8) username: String,
79+
): Boolean {
80+
return username.length >= 3
81+
}
82+
83+
@Property
84+
fun `should be able to login`(@ForAll("usernames") username: String): Boolean {
85+
return username.length >= 3
86+
}
87+
88+
@Property
89+
fun `should validate account`(@ForAll("accounts") account: Account): Boolean {
90+
return account.username.length >= 3
91+
}
92+
93+
@Provide
94+
fun usernames(): Arbitrary<String> = String.any()
95+
.alpha()
96+
.numeric()
97+
.uniqueChars()
98+
.ofLength(3..8)
99+
100+
data class Account(val id: Long, val username: String, val dateOfBirth: LocalDate)
101+
102+
@Provide
103+
fun accounts(): Arbitrary<Account> {
104+
val ids = Long.any().greaterOrEqual(100)
105+
val datesOfBirth = Dates.dates().yearBetween(1900, 2000)
106+
return combine(ids, usernames(), datesOfBirth, combinator = ::Account)
107+
}
108+
109+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.baeldung.propertytesting
2+
3+
class RomanNumeralsConverter {
4+
5+
private val romanPairs = mapOf(
6+
"M" to 1000,
7+
"CM" to 900,
8+
"D" to 500,
9+
"CD" to 400,
10+
"C" to 100,
11+
"XC" to 90,
12+
"L" to 50,
13+
"XL" to 40,
14+
"X" to 10,
15+
"IX" to 9,
16+
"V" to 5,
17+
"IV" to 4,
18+
"I" to 1
19+
)
20+
21+
fun intToRoman(num: Int): String {
22+
if (num < 0 || num > 3999)
23+
throw IllegalArgumentException("the argument has to be in [0, 4_000) range.")
24+
25+
var value = num
26+
val roman = StringBuilder()
27+
28+
for (pair in romanPairs) {
29+
while (value >= pair.value) {
30+
value -= pair.value
31+
roman.append(pair.key)
32+
}
33+
}
34+
return roman.toString()
35+
}
36+
37+
fun romanToInt(roman: String): Int {
38+
var result = 0
39+
var i = 0
40+
while (i < roman.length) {
41+
if (i + 1 < roman.length && romanPairs.containsKey(roman.substring(i, i + 2))) {
42+
result += romanPairs[roman.substring(i, i + 2)]!!
43+
i += 2
44+
} else {
45+
result += romanPairs[roman.substring(i, i + 1)]!!
46+
i++
47+
}
48+
}
49+
return result
50+
}
51+
}

0 commit comments

Comments
 (0)