2012-05-31 3 views
4

В F # Я хочу выполнить модульное тестирование функции с несколькими уровнями вложенных функций. Я хочу иметь возможность тестировать вложенные функции отдельно, но я не знаю, как я мог их вызывать. При отладке каждая из этих вложенных функций вызывается как тип функционального объекта, но я не знаю, могу ли я получить к ним доступ во время компиляции.Могу ли я вызвать вложенные функции для модульного тестирования?

Я не хочу изменять схему вложенности, которую я использую, потому что она имеет наиболее функциональный смысл, чтобы они были вложены таким образом, потому что существует де-факто «наследование» некоторых параметров функции на каждом вложенном уровне.

Возможно ли это как можно? Если нет, какова общая процедура модульных тестирования вложенных функций? Испытываются ли они индивидуально с дополнительными параметрами, а затем вставлены в их вложенные позиции после того, как они не будут снова проверены?

Очень маленький пример:

let range a b = 
    let lower = ceil a |> int 
    let upper = floor b |> int 
    if lower > upper then 
     Seq.empty 
    else 
     seq{ for i in lower..upper -> i} 

Как я могу проверить, что lower или upper правильно работают без изменения вложенную характера кода?

+1

+1 Не возможно, но мне интересно увидеть предложенные методы обхода. : -] – ildjarn

+0

Если 'range' работает правильно, вы не могли бы предположить, что' lower' и 'upper' также работают? – Daniel

+2

@ Даниэль, это простой пример. И на самом деле, не очень хороший, так как нижние и верхние не действительно привязаны к функциям. Но как насчет более сложного примера, когда вложенные вспомогательные функции могут быть довольно сложными сами по себе и поэтому должны быть проверены отдельно от всей содержащейся функции. – mattgately

ответ

7

Я согласен с комментарием Daniels - если внешняя функция работает правильно, вам не нужно проверять какие-либо внутренние функции. Внутренние функции на самом деле являются деталями реализации, которые не должны быть релевантными (особенно в функциональном коде, где вывод не зависит ни от чего, кроме ввода). В C# вы также не проверяете, работает ли цикл for или while внутри вашего метода.

Если и внутренняя, и внешняя функции слишком сложны, возможно, было бы лучше написать внутреннюю функцию как отдельную функцию.

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

Следующий простой пример работает, хотя я не проверял его на что-нибудь более реалистичном:

open NUnit.Framework 

// Function with 'inner' that captures the argument 'a' and takes additional 'x'  
let outer a b = 
    let inner x = x + a + 1 
    (inner a) * (inner b) 

// Unit tests that use reflection in a hacky way to test 'inner' 
[<TestFixture>] 
module Tests = 
    open System 
    open System.Reflection 

    // Runs the specified compiled function - assumes that 'name' of inner functions 
    // is unique in the current assembly (!) and that you can correctly guess what 
    // are the variables captured by the closure (!) 
    let run name closure args = 
    // Lots of unchecked assumptions all the way through... 
    let typ = 
     Assembly.GetExecutingAssembly().GetTypes() 
     |> Seq.find (fun typ -> 
      let at = typ.Name.IndexOf('@') 
      (at > 0) && (typ.Name.Substring(0, at) = name)) 
    let flags = BindingFlags.Instance ||| BindingFlags.NonPublic 
    let ctor = typ.GetConstructors(flags) |> Seq.head 
    let f = ctor.Invoke(closure) 
    let invoke = f.GetType().GetMethod("Invoke") 
    invoke.Invoke(f, args) 

    /// Test that 'inner 10' returns '14' if inside outer where 'a = 3' 
    [<Test>] 
    let test() = 
    Assert.AreEqual(run "inner" [| box 3 |] [| box 10 |], 14) 
+0

Спасибо @ Томас. Мне было интересно, как это можно сделать с отражением. Это, как я ожидал, очень сложный и не стоит делать из-за его очень недружественного синтаксиса для выполнения простых внутренних функций. Я надеялся, что будет более простой способ предоставить аргументы «закрытия» и что тестирование таким образом было бы возможно без изменения структуры программы, что наилучшим образом иллюстрирует иерархию проблемы. – mattgately

+0

+1 для «Внутренние функции - это действительно детали реализации, которые не должны быть релевантными * (особенно в функциональном коде, где вывод не зависит от чего-либо другого, кроме входов) *» (выделение мое). –

+0

@mattgately С небольшим усилием вы могли бы сделать синтаксис приятнее. Используя оператор '?', Вы могли бы получить что-то вроде Assert.AreEqual (funcs? Inner 3 10, 14) 'или если у вас была функция, которая принимает два аргумента закрытия и два фактических аргумента, то что-то вроде' funcs? Inner (3.14, «pi») (1, «hi») '. Но я думаю, что в любом случае не нужно испытывать внутренние функции. –

Смежные вопросы