Проверка равенства функций в модульном тесте F # - PullRequest
3 голосов
/ 22 ноября 2011

У меня есть несколько функций F #, которые реализуют разные алгоритмы для одного и того же ввода, вроде шаблона Стратегии. Чтобы выбрать правильную стратегию, я хочу сопоставить шаблон с входным аргументом и вернуть функцию в качестве значения:

let equalStrategy points : seq<double> =
    ...

let multiplyStrategy factor (points: seq<double>) =
    ...

let getStrategy relationship = 
    match relationship with
        | "="  ->  equalStrategy
        | "*5" ->  multiplyStrategy 5.0
        | _    -> raise (new System.NotImplementedException(" relationship not handled"))

Теперь я хочу написать несколько юнит-тестов, чтобы убедиться, что я вернул правильную стратегию, поэтому я попробовал что-то подобное в nUnit:

    [<TestCase("=")>]
    [<Test>]
    member self.getEqualstrategy( relationship:string ) =            
        let strategy = getStrategy relationship

        Assert.AreEqual( strategy, equalStrategy )

Теперь я думаю, что код верен и будет делать то, что я хочу, но утверждение не выполняется, потому что функции, похоже, не имеют определенной операции равенства. поэтому мои вопросы:

(a) есть ли способ сравнить 2 функции, чтобы увидеть, являются ли они одинаковыми, то есть, пусть isFoo bar = foo == bar, который я могу использовать в утверждении nUnit?

или

(b) есть ли еще одна структура модульного тестирования, которая сделает это утверждение для меня в F #?

Ответы [ 3 ]

12 голосов
/ 22 ноября 2011

Проверка, является ли функция F #, возвращаемая вашим getStrategy, той же функцией, что и одна из определенных вами функций, также практически невозможна.

Чтобы дать некоторые подробности - компилятор F # генерирует класс, который наследуется от FSharpFunc, когда вы возвращаете функцию в качестве значения.Что еще более важно, он генерирует новый класс каждый раз, когда вы создаете значение функции, поэтому вы не можете сравнивать типы классов.

Структура сгенерированных классов выглядит примерно так:

class getStrategy@7 : FSharpFunc<IEnumerable<double>, IEnumerable<double>> {
  public override IEnumerable<double> Invoke(IEnumerable<double> points) {
    // Calls the function that you're returning from 'getStrategy'
    return Test.equalStrategy(points);
  }
}

// Later - in the body of 'getStrategy':
return new getStrategy@7(); // Returns a new instance of the single-purpose class

В принципе, вы можете использовать Reflection, чтобы заглянуть внутрь метода Invoke и найти, какая функция вызывается оттуда, но это не будет надежным решением.

На практике - я думаю, вам, вероятно, следует использовать какой-то другой более простой тест, чтобы проверить, вернула ли функция getStrategy правильный алгоритм.Если вы запускаете возвращенную стратегию на нескольких примерах входных данных, этого должно быть достаточно для проверки правильности возвращаемого алгоритма, и вы не будете полагаться на детали реализации (например, возвращает ли функция getStrategy только именованныйфункция или возвращает ли она новую лямбда-функцию с таким же поведением.

В качестве альтернативы, вы можете обернуть функции в Func<_, _> делегаты и использовать тот же подход, который будет работать в C #. Однако я думаю, что проверка, getStrategy возвращает конкретную ссылку - слишком подробный тест, ограничивающий вашу реализацию.

2 голосов
/ 22 ноября 2011

Компилятору F # было бы очень трудно формально доказать, что две функции всегда имеют одинаковый вывод (при одинаковом вводе).Если бы это было возможно, вы могли бы использовать F #, чтобы доказать математические теоремы довольно тривиально.

В качестве следующего лучшего результата для чистых функций вы можете проверить, что две функции имеют один и тот же выход для достаточно большой выборки различных входных данных,Такие инструменты, как fscheck , могут помочь вам автоматизировать этот тип теста.Я не использовал его, но я использовал scalacheck, основанный на одной и той же идее (оба являются портами из QuickCheck Haskell)

2 голосов
/ 22 ноября 2011

Функции не имеют равенства сравнения:

У вас будет ошибка: Тип '(' a -> 'a)' не поддерживает ограничение равенства, поскольку это тип функции

Есть хороший пост здесь

...