如果你想用第二个版本写东西,你需要创建一个Matcher
封装beCloseTo
匹配器功能的新类:
def computeSameResultsAs[A](g: A => Double,
tolerance: Double = 0.0,
values: Seq[A] = Seq()) = TFMatcher(g, tolerance, values)
case class TFMatcher[A](g: A => Double,
tolerance: Double = 0.0,
values: Seq[A] = Seq()) extends Matcher[A => Double] {
def apply[S <: A => Double](f: Expectable[S]) = {
// see definition below
}
def withTolerance(t: Double) = TFMatcher(g, t, values)
def onValues(tests: A*) = TFMatcher(g, tolerance, tests)
}
此类允许使用您所追求的语法:
val f = (i: Int) => i.toDouble
val g = (i: Int) => i.toDouble + 0.1
"f must be close to another similar function with a tolerance" in {
f must computeSameResultsAs[Int](g).withTolerance(0.5).onValues(1, 2, 3)
}
现在,让我们看看如何beCloseTo
在方法中重用匹配器apply
:
def apply[S <: A => Double](f: Expectable[S]) = {
val res = ((v: A) => beCloseTo(g(v) +/- tolerance).apply(theValue(f.value(v)))).forall(values)
val message = "f is "+(if (res.isSuccess) "" else "not ")+
"close to g with a tolerance of "+tolerance+" "+
"on values "+values.mkString(",")+": "+res.message
result(res.isSuccess, message, message, f)
}
在上面的代码中,我们应用了一个返回 aMatcherResult
到一系列值的函数:
((v: A) => beCloseTo(g(v) +/- tolerance).apply(theValue(f.value(v)))).forall(values)
注意:
f
是一个Expectable[A => Double]
所以我们需要采取它的实际value
能够使用它
同样,我们只能将 anExpectable[T]
应用于 aMatcher[T]
所以我们需要使用该方法theValue
转换f.value(v)
为 an Expectable[Double]
(来自MustExpectations
trait)
最后,当我们有forall
匹配的结果时,我们可以使用以下方法自定义结果消息:
result
构建 a的继承方法MatchResult
(apply
any的方法Matcher
应该返回什么
beCloseTo
如果执行成功,则传递一个布尔值:.isSuccess
根据输入和beCloseTo
匹配的结果消息传递格式良好的“ok”和“ko”消息
将它传递给Expectable
最初用于进行匹配的: f
,以便最终结果的类型为MatchResult[A => Double]
我不确定根据您的要求我们可以得到多少模块化。在我看来,我们在这里能做的最好的事情就是重用beCloseTo
.forall
更新
一个简短的答案可能是这样的:
val f = (i: Int) => i.toDouble
val g = (i: Int) => i.toDouble + 1.0
"f must be close to another similar function with a tolerance" in {
f must computeSameResultsAs[Int](g, tolerance = 0.5, values = Seq(1, 2, 3))
}
def computeSameResultsAs[A](ref: A => Double, tolerance: Double, values: Seq[A]): Matcher[A => Double] = (f: A => Double) => {
verifyFunction((a: A) => (beCloseTo(ref(a) +/- tolerance)).apply(theValue(f(a)))).forall(values)
}
上面的代码会创建一条失败消息,例如:
In the sequence '1, 2, 3', the 1st element is failing: 1.0 is not close to 2.0 +/- 0.5
这应该几乎可以开箱即用。A => MatchResult[_]
缺少的部分是从to的隐式转换Matcher[A]
(我将添加到下一个版本):
implicit def functionResultToMatcher[T](f: T => MatchResult[_]): Matcher[T] = (t: T) => {
val result = f(t)
(result.isSuccess, result.message)
}
如果你想得到所有的失败,你可以使用foreach
而不是:forall
1.0 is not close to 2.0 +/- 0.5; 2.0 is not close to 3.0 +/- 0.5; 3.0 is not close to 4.0 +/- 0.5
更新 2
这每天都变得更好。使用最新的 specs2 快照,您可以编写:
def computeSameResultsAs[A](ref: A => Double, tolerance: Double, values: Seq[A]): Matcher[A => Double] = (f: A => Double) => {
((a: A) => beCloseTo(ref(a) +/- tolerance) ^^ f).forall(values)
}
更新 3
现在有了最新的 specs2 快照,您可以编写:
def computeSameResultsAs[A](ref: A => Double, tolerance: Double, values: Seq[A]): Matcher[A => Double] = (f: A => Double) => {
((a: A) => beCloseTo(ref(a) +/- tolerance) ^^ ((a1: A) => f(a) aka "the value")).forall(values)
}
失败消息将是:
In the sequence '1, 2, 3', the 1st element is failing: the value '1.0' is not close to 2.0 +/- 0.5