Open raniejade opened 5 years ago
Another option which is somewhat dynamic:
val x by memoized<Int>()
val y by memoized<Int>()
val expected by memoized<Int>()
it("$x + $y = $expected") {
assertEqual(expected, x + y)
}
where(
c("x", "y", "expected"),
r( 1 , 2 , 3 ),
r( 2 , 2 , 4 ),
r( 1 , -1 , 0 ),
)
It might be also possible to do:
with { (a, b, c) ->
} where (
r(1, 2, 3),
r(1, 2, 3)
)
// or
with { (a, b, c) ->
} where {
r(1, 2, 3)
r(1, 2, 3)
}
Which options do you consider the best at the moment? I would really like to contribute by implementing it.
@NKb03 I have a local branch that I'm experimenting on. The approach I'm leaning on is the same as what we have in Spek 1.
withData(
r(1, 2, 3),
r(2, -1, 1)
) { (a, b, c) ->
test("$a + $b == $c") { ... }
}
I actually like the approach, which has the lambda expression after the where
the most, because with this approach the data would not have to be actually stored. The where
function could be inline
too, eliminating any performance overhead. The memory and performance improvement may be neglectable although.
@NKb03 the where
approach won't work unless you switch the ordering (do where first - then the with) as the compiler won't have enough information about what the actual parameter types are.
I've been implementing data driven tests with spek for my project and I'd like to share my experience, so that the problems that I faced could perhaps be better handled in your design.
class FooIT : Spek({
setup()
Feature("f") {
Scenario("s") {
val setup by memoized<Setup>()
equalize(
listOf(1, 2, 3, 4, 5),
listOf("a", "b", "c"),
listOf(true, false),
Iterable { iterator { yieldAll(setup.users) } }, // setup cannot be accessed here so it must be evaluated lazily
).forEach {
lateinit var first: Serializable // lots of extra lateinit lines
lateinit var second: Serializable
lateinit var third: Serializable
lateinit var fourth: Serializable
Given("g") {
val iterator = it.iterator()
first = iterator.next()
second = iterator.next()
third = iterator.next()
fourth = iterator.next()
// setup cannot be accessed in the description so you have to do something like this, which is not so good
println("given first $first second $second third $third fourth $fourth")
}
When("w") {
}
Then("t") {
}
}
}
}
})
in short not being able to access memoized in scenario scope causes a lot of problems. this is equalize
in case you are intrested:
fun <T> equalize(vararg iterables: Iterable<T>): Iterable<Sequence<T>> {
val max = iterables.filterIsInstance<Collection<T>>().maxOf { it.size }
return Iterable {
object : Iterator<Sequence<T>> {
var index = 0
val iterators = iterables.map { it.iterator() }.toMutableList()
override fun hasNext() = index < max
override fun next() = sequence {
val listIterator = iterators.listIterator()
while (listIterator.hasNext()) {
val iterable = iterables[listIterator.nextIndex()]
val iterator = listIterator.next()
yield(
when {
iterable is List<T> -> iterable[index % iterable.size]
iterator.hasNext() -> iterator.next()
else -> {
val nextIterator = iterable.iterator()
listIterator.set(nextIterator)
nextIterator.next()
}
}
)
}
}.also { index++ }
}
}
}
The current recommended approach is by using
listOf
and nestedlistOf
s for multiple rows.It has some major caveats:
data[i]
(although you can assign them to a local variable to give it a meaningful name).Spek 1 has a data driven extension but it is tightly coupled into the DSL and is very opinionated to particular usage of Spek (
on
).I'd like to bring some form of the Spek 1 plugin into
2.x
:The whole implementation is the following: