我有函数A => Double
。我想检查两个这样的函数是否为给定的值集提供相同的结果(使用现有的beCloseTo
匹配器的公差)。
我希望能写下来:
type TF = A => Double
(f: TF) must computeSameResultsAs(g: TF,tolerance: Double, tests: Set[A])
我想以模块化的方式构建这个匹配器,而不是简单地从头开始编写Matcher[TF]
。
如果我能写的话可能会更好:
(f: TF) must computeSameResultsAs(g: TF)
.withTolerance(tolerance)
.onValues(tests: Set[A])
此外,我想在匹配器失败时获得合理的描述。
睡了之后,我想出了以下内容。
def computeSameResultsAs[A](ref: A => Double, tolerance: Double, args: Set[A]): Matcher[A => Double] =
args.map(beCloseOnArg(ref, tolerance, _)).reduce(_ and _)
def beCloseOnArg[A](ref: A => Double, tolerance: Double, arg: A): Matcher[A => Double] =
closeTo(ref(arg), tolerance) ^^ ((_: A => Double).apply(arg))
这比Eric的解决方案短得多,但没有提供良好的失败信息。我希望能够在第二种方法中重命名映射值。像下面的东西(不编译)。
def beCloseOnArg[A](ref: A => Double, tolerance: Double, arg: A): Matcher[A => Double] =
closeTo(ref(arg), tolerance) ^^ ((_: A => Double).apply(arg) aka "result on argument " + arg)
答案 0 :(得分:9)
如果你想用第二个版本编写东西,你需要创建一个新的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)
}
在上面的代码中,我们应用了一个返回MatcherResult
to a sequence of values:
((v: A) => beCloseTo(g(v) +/- tolerance).apply(theValue(f.value(v)))).forall(values)
请注意:
f
是Expectable[A => Double]
,因此我们需要将其实际的value
用于
类似地,我们只能将Expectable[T]
应用于Matcher[T]
,因此我们需要使用方法theValue
将f.value(v)
转换为Expectable[Double]
(来自MustExpectations
特质)
最后,当我们得到forall
匹配的结果时,我们可以使用以下方法自定义结果消息:
构建result
的继承MatchResult
方法(任何apply
的{{1}}方法应返回
传递一个布尔值,说明Matcher
的执行是否成功:beCloseTo
根据.isSuccess
匹配
首先传递用于进行匹配的beCloseTo
: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[_]
的隐式转换(我将添加到下一个版本):
Matcher[A]
如果您想要解决所有问题,可以使用foreach
代替implicit def functionResultToMatcher[T](f: T => MatchResult[_]): Matcher[T] = (t: T) => {
val result = f(t)
(result.isSuccess, result.message)
}
:
forall
更新2
每天都变得更好。使用最新的specs2快照,您可以写:
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
更新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) ^^ f).forall(values)
}
失败消息将是:
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)
}