2012-05-31 233 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} 

我怎么能测试lowerupper是在不改变代码的嵌套性质正常工作?

+1

+1不可能的,但我很好奇,看看所建议的解决方法。 : - ] – ildjarn

+0

如果'range'正常工作,你不能假设'lower'和'upper'做得好吗? – Daniel

+2

@丹尼尔,这是一个简单的例子。实际上,这并不是一个很好的方法,因为低级和高级不会真正绑定到函数中。但是,更复杂的例子呢,嵌套的帮助函数本身可能相当复杂,因此应该从整个包含函数中进行测试。 – mattgately

回答

7

我同意丹尼尔斯的评论 - 如果外层函数正常工作,则不需要测试任何内层函数。内部函数实际上是一个不应该相关的实现细节(特别是在函数代码中,输出不依赖于输入以外的任何内容)。在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

谢谢@Tomas。我想知道如何用反射来完成。正如我所预料的那样,它是非常复杂和不值得的,因为它执行简单的内部函数的语法非常不友好。我希望能够提供“封闭”论点的更简单的方法,并且可以在不修改程序结构的情况下以这种方式进行测试,该程序结构以最佳方式说明了问题的层次结构。 – mattgately

+0

+1“内部函数实际上是一个不应该相关的实现细节*(特别是在函数代码中,输出不依赖于输入以外的任何内容)*”(重点是我的)。 –

+0

@mattgately通过一点努力,你可以使语法更好。使用'''运算符,你可能会得到像'Assert.AreEqual(funcs?inner 3 10,14)'这样的东西,或者如果你的函数需要两个闭包参数和两个实参,那么funcs?inner(3.14, “pi”)(1,“hi”)'。但我认为不应该需要测试内部功能。 –

相关问题