2012-05-31 452 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」)'。但我認爲不應該需要測試內部功能。 –

相關問題