8

私は機能を持っていますA => DoublebeCloseToこのような2つの関数が、特定の値のセットに対して同じ結果(許容範囲まで、既存のマッチャーを使用)を提供するかどうかを確認したいと思います。

私は書くことができるようになりたい:

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のソリューションよりもはるかに短いですが、適切な失敗メッセージを提供しません。私ができるようにしたいのは、2番目の方法でマップされた値の名前を変更することです。次のようなもの(コンパイルされません)。

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)
4

1 に答える 1

9

2番目のバージョンで物事を書きたい場合は、マッチャー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 上記のコードでは、値のシーケンスにaを返す関数を適用します。

((v: A) => beCloseTo(g(v) +/- tolerance).apply(theValue(f.value(v)))).forall(values)

ご了承ください:

  1. fですExpectable[A => Double]ので、実際valueに使用できるようにする必要があります

  2. Expectable[T]同様に、にのみ適用できるMatcher[T]ので、メソッドを使用して(トレイトから)theValueに変換する必要がありますf.value(v)Expectable[Double]MustExpectations

最後に、forallマッチングの結果が得られたら、次を使用して結果メッセージをカスタマイズできます。

  1. 継承されたresultメソッドの構築MatchResultapplyいずれかのメソッドがMatcher返す必要があるもの

  2. beCloseToの実行が成功したかどうかを言うブール値を渡します。.isSuccess

  3. beCloseTo入力とマッチングの結果メッセージに基づいて、適切にフォーマットされた「ok」メッセージと「ko」メッセージを渡します。

  4. 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]です(これは次のバージョンに追加します)。

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
于 2011-10-27T23:31:13.823 に答える