【问题标题】:Can I invoke nested functions for unit testing?我可以调用嵌套函数进行单元测试吗?
【发布时间】:2012-05-31 20:17:24
【问题描述】:

在 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 不可能,但我很想看到建议的解决方法。 :-]
  • 如果range 工作正常,你难道不能假设lowerupper 也能正常工作吗?
  • @Daniel,这是一个简单的例子。实际上,这不是一个很好的方法,因为 lower 和 upper 并没有真正绑定到函数中。但是,如果有一个更复杂的例子,嵌套的辅助函数本身可能非常复杂,因此应该与整个包含函数分开进行测试。
  • @mattgately 您可能想重新审视“单元测试”一词中“单元”的定义。如果有问题的单元是一个函数,那么你不是在测试内部功能——你是在测试函数。我的意思是,如果您需要测试 lower 和 upper(或它们的一些类似物),请让它们成为自己的独立代码单元。
  • 实际上它们是独立的单元:ceilfloor。它们是标准库的一部分。无需在您身边进行测试。

标签: unit-testing testing f# functional-testing


【解决方案1】:

我同意 Daniels 的评论 - 如果外部功能正常工作,您不需要测试任何内部功能。内部函数实际上是一个不应该相关的实现细节(特别是在函数代码中,输出不依赖于输入以外的任何东西)。在 C# 中,您也无需测试方法内的 for 循环或 while 循环是否正常工作。

如果内部函数和外部函数都太复杂,那么将内部函数写成单独的函数可能会更好。

也就是说,您当然可以使用反射来处理已编译的程序集并调用内部函数。内部函数被编译为具有构造函数的类,该构造函数采用 closure(外部函数的捕获值)和采用实际参数的 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)

【讨论】:

  • 谢谢@Tomas。我想知道如何通过反射来做到这一点。正如我所预料的那样,它非常复杂且不值得做,因为它的语法对于执行简单的内部函数非常不友好。我希望有一种更简单的方法来提供“闭包”参数,并且在不修改程序结构的情况下以这种方式进行测试是可能的,这以最好的方式说明了问题的层次结构。
  • +1 表示“内部函数实际上是一个不应该相关的实现细节(尤其是在函数代码中,输出不依赖于输入以外的任何东西)”(强调我的)。
  • @mattgately 通过一些努力,您可以使语法更好。使用? 运算符,你可能会得到类似Assert.AreEqual(funcs?inner 3 10, 14) 的东西,或者如果你有一个接受两个闭包参数和两个实际参数的函数,那么类似funcs?inner (3.14, "pi") (1, "hi")。但我认为无论如何都不需要测试内部函数。
  • @mattgately 以类似方式使用?的相关示例,请参见这篇文章:msdn.microsoft.com/en-us/library/hh304373
  • 我仍然会感兴趣,如果有人有这样做的方法,即使它不是严格的单元测试。原因是我相信功能范式允许我以最直接代表手头实际问题的方式实现程序。另外,我相信如果一个函数真的只在一个函数中使用(并且共享输入),那么就有一个自然的层次结构可以使用是有道理的。我宁愿不修改自然结构以促进测试。也许有人对如何将测试融入自然嵌套层次结构有建议?
猜你喜欢
  • 2011-07-09
  • 2020-06-09
  • 2010-09-22
  • 1970-01-01
  • 2020-01-26
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多