假设我在 Kotlin 中有以下数据结构(根据实际需要进行了简化):
data class RootClass(
val a: String,
val nestedContent: MiddleClass,
) : Result
data class MiddleClass(
val foo: String,
val leaf: Leaf,
)
data class Leaf(
val value: String,
val leaf: Leaf?,
)
出于测试中断言/验证的目的,是否有某种方法(例如使用某些库)以声明性方式定义预期结果,例如允许忽略任意字段。这样预期的结果可以通过以下方式呈现出来
RootClass(
a = "Foo",
nestedContent = MiddleClass(
foo = anyString(),
leaf = Leaf(
value = "leaf-value",
leaf = any(Leaf)
)
)
)
我对assertK、Hamcrest 和mockK 匹配器等进行了一些研究,虽然还不是很深入,但还没有找到“最佳方法”。对于典型的用例,通常可以例如创建自定义帮助程序来检查所需的字段,但针对上述内容进行测试有时会很有用且易于阅读。
如果您想要声明式,类型安全构建器模式非常有用。
首先,声明一个代表匹配器的接口。
interface Matcher<in T> {
fun match(value: T): Boolean
}
然后您可以为此创建各种实现(例如,请参阅 Hamcrest 中可用的各种匹配器)。现在,我只需要这些简单的:
data class EqualityMatcher<T>(val value: T): Matcher<T> {
override fun match(value: T) = value == this.value
}
object AnyNonNull: Matcher<Any?> {
override fun match(value: Any?) = value != null;
}
data class PropertyMatcher<T, V>(
val property: KProperty1<T, V>,
val downstream: Matcher<V>
): Matcher<T> {
override fun match(value: T) = downstream.match(property.get(value))
}
然后我们可以编写一个构建器来构建一种新型匹配器,仅当
Matcher<T>
列表包含所有匹配项时才匹配。
data class ComposedMatcher<T>(val matchers: MutableList<Matcher<T>> = mutableListOf()): Matcher<T> {
override fun match(value: T) = matchers.all { it.match(value) }
infix fun <V> KProperty1<T, V>.shouldBe(value: V) {
matchers.add(PropertyMatcher(this, EqualityMatcher(value)))
}
infix fun <V> KProperty1<T, V>.shouldMatch(matcher: Matcher<V>) {
matchers.add(PropertyMatcher(this, matcher))
}
inline infix fun <V> KProperty1<T, V>.shouldMatch(block: ComposedMatcher<V>.() -> Unit) {
matchers.add(PropertyMatcher(this, match(block)))
}
}
inline fun <T> match(block: ComposedMatcher<T>.() -> Unit): Matcher<T> =
ComposedMatcher<T>().apply(block)
仅此而已,我们就可以创建一个像您问题中的那样的
Matcher<RootClass>
:
val matcher = match {
RootClass::a shouldBe "Foo"
RootClass::nestedContent shouldMatch {
MiddleClass::foo shouldMatch AnyNonNull
MiddleClass::leaf shouldMatch {
Leaf::value shouldBe "leaf-value"
Leaf::leaf shouldMatch AnyNonNull
}
}
}
通过对名称进行一些调整,您可以使其更具可读性。
作为扩展,尝试创建一个
anyOf
匹配器,当其匹配器列表中的任何匹配器匹配时进行匹配。