CLOVIS
09/10/2024, 9:52 AM@ParameterizedTest
@MethodSource("stringIntAndListProvider")
fun `test with multiple arguments`(str: String, num: Int, list: List<String>) {
…test code…
}
companion object {
@JvmStatic
fun stringIntAndListProvider(): Stream<Arguments> {
val strings = listOf("apple", "lemon")
val nums = listOf(1, 2)
val lists = listOf(listOf("a", "b"), listOf("x", "y"))
return strings.stream().flatMap { string ->
nums.stream().flatmap { num ->
lists.stream().flatMap { list ->
Stream.of(arguments(string, num, list))
}
}
}
}
}
Quite a bit verbose! Let's see if we can do better…
Prepared (test framework) + Parameterize (parameterization DSL):
fun SuiteDsl.testWithMultipleArguments() = suite {
parameterize {
val string by parameterOf("apple", "lemon")
val num by parameterOf(1, 2)
val list by parameterOf(listOf("a", "b"), listOf("x", "y"))
test("Test $string $num $list") {
…test code…
}
}
}
Kotlin's DSL capabilities really spoil us K
Learn more:
• Writing parameterized tests in Kotlin
• Parameterize: documentation • #C06083PAKEK
• Prepared: documentation • #C078Z1QRHL3Vampire
09/10/2024, 10:14 AMdef 'Test #string #num #list'() {
//…test code…
where:
string | num | list
'apple' | 1 | ['a', 'b']
'lemon' | 2 | ['x', 'y']
}
😄dave08
09/10/2024, 10:16 AMdave08
09/10/2024, 10:18 AMVampire
09/10/2024, 10:18 AMVampire
09/10/2024, 10:18 AMVampire
09/10/2024, 10:19 AMVampire
09/10/2024, 10:20 AMdave08
09/10/2024, 10:23 AMdave08
09/10/2024, 10:24 AMVampire
09/10/2024, 10:41 AMchristophsturm
09/10/2024, 10:42 AMval tests = testsAbout("String#reverse") {
listOf(Pair("otto", "otto"), Pair("racecar", "racecar")).forEach { (input, output) ->
it("reverses $input to $output") {
assert(input.reversed() == output)
}
}
}
https://github.com/failgood/failgood/blob/main/docs/parametrized%20tests.mdchristophsturm
09/10/2024, 10:45 AMCLOVIS
09/10/2024, 10:59 AMCLOVIS
09/10/2024, 11:00 AMparameterize {
val x by parameterOf(0, 1, -2)
val y by parameterOf(0, 1, -2, x, x+1, x-1)
…
}
it doesn't look like you are allowed to refer to other parameters 🤔Arjan van Wieringen
09/10/2024, 11:02 AMCLOVIS
09/10/2024, 11:03 AMVampire
09/10/2024, 11:05 AMdef 'Test #string #num #list'() {
//…test code…
where:
string | _
'apple' | _
'lemon' | _
combined:
num | _
1 | _
2 | _
combined:
list | _
['a', 'b'] | _
['x', 'y'] | _
}
or
def 'Test #string #num #list'() {
//…test code…
where:
string << ['apple', 'lemon']
combined:
num << [1, 2]
combined:
list << [['a', 'b'], ['x', 'y']]
}
Vampire
09/10/2024, 11:06 AMVampire
09/10/2024, 11:09 AMit doesn't look like you are allowed to refer to other parametersYes, you can derive data variables from others in Spock. But anyway, let's not strive off to discuss Spock features, that was not my intention. 😄
CLOVIS
09/10/2024, 11:18 AMCLOVIS
09/10/2024, 11:18 AMYoussef Shoaib [MOD]
09/10/2024, 12:01 PMCLOVIS
09/10/2024, 12:27 PMCLOVIS
09/10/2024, 12:27 PMCLOVIS
09/10/2024, 12:28 PMprovideDelegate
and sequencesBen Woodworth
09/10/2024, 1:09 PMparameterize
is re-running the block, having provideDelegate
return different values each time, while providing a way to avoid unnecessarily re-running code with a lazy parameter {}
. And the library itself is designed to work for more than just testing, so it might be worth a look!Johann Pardanaud
09/10/2024, 1:21 PMYoussef Shoaib [MOD]
09/10/2024, 1:27 PMLaurent Thiebaud
09/11/2024, 7:46 AMprivate fun runTheTest(param1, param2, ...) {}
@Test fun test1() = runTheTest(..., ...)
@Test fun test2() = runTheTest(..., ...)
This way intellij helps you identify the failing case.
(this in simple cases, parameterized tests may still be relevant)christophsturm
09/11/2024, 7:57 AMLaurent Thiebaud
09/11/2024, 8:03 AMIntelliJ doesn't know which lines are tests or not, so it cannot display the small green triangle to select which tests to execute.So I guess it is the same (yet I didn't know we could rerun like this 🙏 )
christophsturm
09/11/2024, 8:15 AMchristophsturm
09/11/2024, 8:16 AMCLOVIS
09/11/2024, 12:47 PMAt least that's usually the case with tests frameworks (tested JUnit and Kotest) […] PreparedIn the case of Kotest and Prepared, parameterized tests are declared in lambdas, so they should appear in the stacktrace similarly to your example with a private function.
Oliver.O
09/18/2024, 10:17 AMsuite
, these would be executed even if none of the tests in the suite were active (due to conditions, filtering), right? If so, it would slow down running a subset of tests if parameter generation is expensive. Also, `suite`s would not accept suspending functions, right? All of that might matter or not, depending on the context.CLOVIS
09/18/2024, 12:17 PMIf so, it would slow down running a subset of tests if parameter generation is expensive.Yes, though I haven't seen a case where parameter generation is expensive. Unlike Kotest, which allows suspension in
context
, Prepared doesn't allow suspend
in suite
. Instead, complex operations are encapsulated as values:
val users by parameterOf("abcd", "efgh")
.prepare { Database.loadUser(it) }
The operation is only executed inside the test proper, so if the test is disabled, it's not executed at all.
In general, Prepared isn't aware of tests being enabled or not, it just declares the tests to the underlying runner as-is. That's why the !
syntax works with Kotest, etc.CLOVIS
09/18/2024, 12:20 PMThis looks somewhat similar to what has been proposed for KotestNot surprising! @Ben Woodworth is the origin of it, here. He later created a library specifically for this, #C06083PAKEK. I had a similar idea in my mind for a long time but couldn't get it to work, so Prepared just adds an integration with his library.
Oliver.O
09/18/2024, 1:21 PMCLOVIS
09/18/2024, 3:11 PMCLOVIS
09/18/2024, 3:15 PMclass TestEnvironment internal constructor(
val testName: String,
val coroutineScope: TestScope, // from kotlinx.coroutines
) {
internal val cache = Cache()
internal val finalizers = Finalizers()
}
Cache
is used to implement prepared
, and everything else builds on top of thatCLOVIS
09/18/2024, 3:17 PMOliver.O
09/18/2024, 3:18 PMCLOVIS
09/18/2024, 3:20 PMsuspend
? If so, I'll need the same flattening tricks I use with the Kotest plugin…Oliver.O
09/18/2024, 3:22 PMOliver.O
09/18/2024, 3:30 PMbeforeEach
, but KGP swallows that). Also, KGP is opinionated on what it considers legitimate test reporting (e.g. nested suites are possible with TeamCity reports, which I've tried, but not with the crippled TC report variant used inside kotlin-test).CLOVIS
09/18/2024, 3:33 PMCLOVIS
09/18/2024, 3:34 PMCLOVIS
09/18/2024, 3:37 PMOliver.O
09/18/2024, 4:09 PMsuspend fun runTests()
on each platform and a standard reporting format, including parallel test runs and arbitrary nesting.
So I think a framework is better off if it does not just describe tests but also runs them according to its own needs, not those of other frameworks.
Assertions are a different thing, as long as they stick to established protocols. I like Kotest's fluent assertion style more than boilerplate assert...
invocations. What I don't like as much is that Kotest deviates from standard exception handling, as it requires its matchers to do special stuff when throwing to support withClue
for example.
I'd have no problem of providing a compiler plugin for test discovery as that's just a backend IR plugin where things are more stable. But for significant adoption I guess an IDE plugin would be required, and my priorities to not seem to leave sufficient room for that.christophsturm
09/26/2024, 8:22 AMPlugin [id: 'io.kotest.multiplatform', version: '6.0.0-20240905.065253-61'] was not found in any of the following sources:
CLOVIS
09/26/2024, 8:49 AMchristophsturm
09/26/2024, 8:51 AM