Если вы хотите писать вещи со второй версией, вам нужно создать новый класс 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
к последовательности значений :
((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
метод любого Matcher
передавая логическое выражение, если выполнение beCloseTo
было успешным: .isSuccess
передача его в хорошо отформатированных сообщениях "ok" и "ko" на основе входных данных и сообщения результата beCloseTo
, соответствующего
передавая ему Expectable
, который использовался для сопоставления, в первую очередь: f
, так что конечный результат имеет тип MatchResult[A => Double]
Я не уверен, насколько модульными мы можем стать, учитывая ваши требования. Мне кажется, что лучшее, что мы можем здесь сделать, это повторно использовать beCloseTo
с forall
.
UPDATE
Более короткий ответ может быть примерно таким:
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