We all know that you shouldn't assert exact equality for floating-point numbers. assertk
comes with a convenient way to do it better:
assertThat(value).isCloseTo(3.0, 0.000001)
I'd like to extend that to arrays:
assertThat(array).isCloseTo(doubleArrayOf(2.0, 3.0), 0.000001)
But the best I can do is:
fun Assert<DoubleArray>.isCloseTo(expected: DoubleArray, delta: Double) {
hasSameSizeAs(expected)
this.matchesPredicate { actual : DoubleArray ->
expected.indices.forEach { index ->
if (!actual[index].isCloseTo(expected[index], epsilon)) {
return@matchesPredicate false
}
}
true
}
}
fun Double.isCloseTo(expected: Double, epsilon: Double): Boolean {
abs(this - expected) <= epsilon
}
Is there any way to reduce this matchesPredicate
to something like (pseudocode):
hasElementsMatchingPredicate { index, actual ->
actual.isCloseTo(expected, epsilon)
}
To answer the question in the title:
assert that an array's elements each match a predicate
You could write a new AssertK assertion for DoubleArray
:
fun Assert<DoubleArray>.allMatchPredicate(f: (Double) -> Boolean) = given { actual ->
if (actual.all(f)) return
val rejected = actual.filterNot(f)
expected("all items to satisfy the predicate, but these items didn't: ${show(rejected)}")
}
Then use it like this:
@Test
fun test() {
val positive = doubleArrayOf(1.0, 2.0, 3.0)
val mixed = doubleArrayOf(-1.0, 1.0, -4.0)
assertThat(positive).allMatchPredicate { it > 0.0 }
assertThat(mixed).allMatchPredicate { it > 0.0 }
}
Result:
org.opentest4j.AssertionFailedError: expected all items to satisfy the predicate, but these items didn't: <[-1.0, -4.0]>
For comparing two DoubleArrays
, as the question body asks for, you could do this:
fun Assert<DoubleArray>.allCloseTo(expected: DoubleArray, delta: Double) = given { actual ->
if (actual.size != expected.size)
expected("array to have size ${expected.size} but was ${actual.size}")
val rejected = actual.asSequence()
.withIndex().filterNot {
// adapted from Assert<Double>.isCloseTo
it.value >= expected[it.index].minus(delta) &&
it.value <= expected[it.index].plus(delta)
}
.map { it.value }.toList()
if (rejected.isNotEmpty())
expected("all items to satisfy the predicate, but these items didn't: ${show(rejected)}")
}
Usage:
@Test
fun test() {
val same = doubleArrayOf(1.001, 2.001, 3.001)
val diff = doubleArrayOf(1.1, 2.001, 3.1)
val expected = doubleArrayOf(1.0, 2.0, 3.0)
assertThat(same).allCloseTo(expected, 0.01)
assertThat(diff).allCloseTo(expected, 0.01)
}
Result:
org.opentest4j.AssertionFailedError: expected all items to satisfy the predicate, but these items didn't: <[1.1, 3.1]>