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

4

1 に答える 1

7

Daniels のコメントに同意します。外部関数が正しく機能する場合は、内部関数をテストする必要はありません。内部関数は、実際には関連するべきではない実装の詳細です (特に、出力が入力以外に依存しない機能的なコードでは)。forC# では、メソッド内のループまたは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)
于 2012-05-31T21:06:40.123 に答える