【问题标题】:Creating a Specs2 matcher in a modular way以模块化方式创建 Specs2 匹配器
【发布时间】:2011-12-16 12:54:07
【问题描述】:

我有函数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)

【问题讨论】:

    标签: scala specs specs2


    【解决方案1】:

    如果你想用第二个版本写东西,你需要创建一个新的 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)          
    }
    

    现在,让我们看看如何在apply 方法中重用beCloseTo 匹配器:

    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)
    

    注意:

    1. f 是一个Expectable[A =&gt; Double],所以我们需要获取它的实际value 才能使用它

    2. 1234563 p>

    最后,当我们有forall匹配的结果时,我们可以通过使用自定义结果消息:

    1. 继承的result 方法构建MatchResult(任何Matcherapply 方法应该返回什么

    2. 传递一个布尔值表示beCloseTo 的执行是否成功:.isSuccess

    3. 根据输入和beCloseTo 匹配的结果消息传递格式良好的“ok”和“ko”消息

    4. 将用于匹配的Expectable传递给它:f,因此最终结果的类型为MatchResult[A =&gt; Double]

    我不确定根据您的要求,我们能获得多少模块化。在我看来,我们在这里能做的最好的事情就是重复使用 beCloseToforall

    更新

    一个简短的答案可能是这样的:

    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 =&gt; MatchResult[_]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
    

    【讨论】:

    • 感谢您的回答埃里克。你的建议有效,但基本上写了一个新的匹配器。我在我的问题中添加了一种创建匹配器的更短的方法(更像我的想法),但它缺少正确的消息。
    • 有没有机会添加该功能来“装饰”^^ 转换与类似 aka 的东西?毕竟当拥有^^ (A =&gt; B) 时,你需要解释你是如何从A 获得B 的,比如“如果为某些集合做^^ (_.head) 时的第一个元素。
    猜你喜欢
    • 2013-03-19
    • 1970-01-01
    • 1970-01-01
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多