FsUnit и проверка равенства чисел с плавающей точкой

Я начал использовать FsUnit для тестирования кода F#. Это позволяет выразить утверждение в стиле F#, например:

[<Test>]
member this.``Portugal voted for 23 countries in 2001 Eurovision contest``() =
    this.totalVotes 
    |> getYearVotesFromCountry "Portugal" 2001
    |> Seq.length
    |> should equal 23

Обратите внимание: "должно быть равно 23", что я получаю от FsUnit. Вот как FsUnit определяет это:

пусть равно x = новое EqualConstraint(x)

С числами с плавающей запятой это не так просто. Я должен использовать EqualConstraint с внутри метода. Это естественно подходит для C#:

Assert.That(result).Is.EqualTo(1).Within(0.05);

Конечно, я хотел бы иметь возможность писать на F#:

result |> should equal 1 within 0.05

Но это не работает. Я определил новую функцию:

let almostEqual x = (new EqualConstraint(x)).Within(0.01)

или если я хочу параметризовать точность, я могу указать ее в качестве второго аргумента:

let equalWithin x y = (new EqualConstraint(x)).Within(y)

Но ни один из них не симпатичен. Я хотел бы определить "внутри" функцию более естественным образом для F#, чтобы она могла использоваться вместе с равными. F# не поддерживает перегрузку методов, так что, похоже, я не могу определить его таким образом, чтобы "равные" можно было использовать по отдельности или вместе с "внутри".

Есть идеи?

2 ответа

Решение

Это интересная проблема! Я не думаю, что вы можете добавить within 0.05 к существующему определению should equal в любом случае. Для этого вам нужно добавить параметр в should функция, но для этого необходимо иметь фиксированное количество параметров в библиотеке.

Один из способов написать это элегантно в F# - создать пользовательский оператор +/-, Обратите внимание, что вам все еще нужно использовать скобки, но это выглядит довольно аккуратно:

0.9 |> should equal (1.0 +/- 0.5)

Оператор просто создает некоторое значение специального типа, которое должно быть явно обработано в equal функция. Вот реализация:

type Range = Within of float * float
let (+/-) (a:float) b = Within(a, b)

let equal x = 
  match box x with 
  | :? Range as r ->
      let (Within(x, within)) = r
      (new EqualConstraint(x)).Within(within)
  | _ ->
    new EqualConstraint(x)

Примечание по терминологии: F# поддерживает перегрузку метода; он не поддерживает перегрузку функций let-bound ("свободные" функции, определенные в модулях).

Вы могли бы, например, сделать это should.equal с точкой, так что equal это метод на should объект, который вы могли бы перегрузить. Хотя это все равно не поможет, так как вы не можете перегружать аргументы карри. Хммм.

Ладно, мне нечего предложить. Лично мне не нравятся эти типы синтаксических сахаров в библиотеках.

Другие вопросы по тегам