F# F中NUnit 3测试的测试用例#

F# F中NUnit 3测试的测试用例#,f#,testcase,nunit-3.0,F#,Testcase,Nunit 3.0,我正试图使用NUnit为一个F#项目建立一个测试套件。 看起来,特别是在测试解析器和类型检查器时,通常会有一个有效输入数据列表和一个无效数据列表。 测试本身实际上是相同的,因此我正在寻找一种聪明的方法来避免为每个数据项编写测试函数,而是将测试函数与数据分开。 显然,似乎有一种叫做测试用例的东西,但我很难找到有关使用NUnit 3 with F#的全面文档,特别是我的场景的最佳实践示例 所有的指示和提示都会得到高度重视 这是NUnit 3.x的更新答案,因为我的原始答案显示了NUnit 2.x示例

我正试图使用NUnit为一个F#项目建立一个测试套件。 看起来,特别是在测试解析器和类型检查器时,通常会有一个有效输入数据列表和一个无效数据列表。 测试本身实际上是相同的,因此我正在寻找一种聪明的方法来避免为每个数据项编写测试函数,而是将测试函数与数据分开。 显然,似乎有一种叫做测试用例的东西,但我很难找到有关使用NUnit 3 with F#的全面文档,特别是我的场景的最佳实践示例


所有的指示和提示都会得到高度重视

这是NUnit 3.x的更新答案,因为我的原始答案显示了NUnit 2.x示例

下面的例子并不全面,但足以让你突破门槛,开始跑步。值得注意的是,测试函数是使用参数列表而不是curry编写的。此外,有几种方法可以使用NUnit 3.x属性生成测试数据,例如,但遗憾的是,没有一种可用的属性知道如何为受歧视的联合生成测试数据

另外,FSUnit是不需要的,我没有尝试让它工作,因为NUnint 2.x和3.x之间的差异是如此巨大,以至于我很高兴下面的示例能够工作。也许将来我会更新这个答案

namespace NUnit3Demo

open NUnit.Framework

module MyTest = 
    // ----------------------------------------------------------------------

    [<Pairwise>]
    let pairWiseTest([<Values("a", "b", "c")>] (a : string), [<Values("+", "-")>] (b :string), [<Values("x", "y")>] (c : string))
        = printfn "%A %A %A" a b c

    // ----------------------------------------------------------------------

    let divideCases1 =
        [
            12, 3, 4
            12, 2, 6
            12, 4, 3
        ] |> List.map (fun (q, n, d) -> TestCaseData(q,n,d))

    [<TestCaseSource("divideCases1")>]
    let caseSourceTest1(q:int, n:int, d:int) =
        Assert.AreEqual( q, n / d )

    // ----------------------------------------------------------------------

    let divideCases2 =
        seq {
            yield (12, 3, 4)
            yield (12, 2, 6)
            yield (12, 4, 3)
        }

    [<TestCaseSource("divideCases2")>]
    let caseSourceTest2(q:int, n:int, d:int) =
        Assert.AreEqual( q, n / d )

    // ----------------------------------------------------------------------

    [<TestCase(12,3,4)>]
    [<TestCase(12,2,6)>]
    [<TestCase(12,4,3)>]
    let testCaseTest(q:int, n:int, d:int) =
        Assert.AreEqual( q, n / d )

    // ----------------------------------------------------------------------

    let evenNumbers : int [] = [| 2; 4; 6; 8 |]

    [<TestCaseSource("evenNumbers")>]
    let caseSourceTest3 (num : int) =
        Assert.IsTrue(num % 2 = 0)
namespace NUnit3Demo
打开NUnit.Framework
模块MyTest=
// ----------------------------------------------------------------------
[]
让成对测试([](a:string),[](b:string),[](c:string))
=printfn“%A%A%A”A b c
// ----------------------------------------------------------------------
让我们把案件分开=
[
12, 3, 4
12, 2, 6
12, 4, 3
]|>List.map(fun(q,n,d)->TestCaseData(q,n,d))
[]
让caseSourceTest1(q:int,n:int,d:int)=
断言.等于(q,n/d)
// ----------------------------------------------------------------------
让我们把箱子分开=
序号{
收益率(12,3,4)
收益率(12,2,6)
收益率(12,4,3)
}
[]
让caseSourceTest2(q:int,n:int,d:int)=
断言.等于(q,n/d)
// ----------------------------------------------------------------------
[]
[]
[]
让testCaseTest(q:int,n:int,d:int)=
断言.等于(q,n/d)
// ----------------------------------------------------------------------
设偶数:int[]=[| 2;4;6;8 |]
[]
让caseSourceTest3(num:int)=
Assert.IsTrue(数量%2=0)
保留原始答案,因为OP在其他答案中指出了它

下面的示例是3年前使用NUnit 2.x编写的,所以它有点过时,但应该给您一个理想的版本

您创建一个测试数据数组,然后索引到该数组中以提取测试值和预期结果。这样做的好处是,您不会为一个函数编写大量的单独测试

这是我们几年前做的一件事

open NUnit.Framework
open FsUnit

let private filterValues : (int list * int list)[] = [| 
    (
        // idx 0
        // lib.filter.001
        [],
        []
    ); 
    (
        // idx 1
        // lib.filter.002
        [-2],
        [-2]
    );
    (
        // idx 2
        // lib.filter.003
        [-1],
        []
    );
    (
        // idx 3
        // lib.filter.004
        [0],
        [0]
    );
    (
        // idx 4
        // lib.filter.005
        [1],
        []
    );
    (
        // idx 5
        // lib.filter.006
        [1; 2],
        [2]
    );
    (
        // idx 6
        // lib.filter.007
        [1; 3],
        []
    );
    (
        // idx 7
        // lib.filter.008
        [2; 3],
        [2]
    );
    (
        // idx 8
        // lib.filter.009
        [1; 2; 3],
        [2]
    );
    (
        // idx 9
        // lib.filter.010
        [2; 3; 4],
        [2; 4]
    );
    |]

[<Test>]
[<TestCase(0, TestName = "lib.filter.01")>]
[<TestCase(1, TestName = "lib.filter.02")>]
[<TestCase(2, TestName = "lib.filter.03")>]
[<TestCase(3, TestName = "lib.filter.04")>]
[<TestCase(4, TestName = "lib.filter.05")>]
[<TestCase(5, TestName = "lib.filter.06")>]
[<TestCase(6, TestName = "lib.filter.07")>]
[<TestCase(7, TestName = "lib.filter.08")>]
[<TestCase(8, TestName = "lib.filter.09")>]
[<TestCase(9, TestName = "lib.filter.10")>]
let ``List filter`` idx = 
    let (list, _) = filterValues.[idx]
    let (_, result) = filterValues.[idx]
    List.filter (fun x -> x % 2 = 0) list 
    |> should equal result
    filter (fun x -> x % 2 = 0) list 
    |> should equal result
打开NUnit.Framework
开放式FSU
让私有filterValues:(int list*int list)[]=[|
(
//idx 0
//lib.filter.001
[],
[]
); 
(
//idx 1
//lib.filter.002
[-2],
[-2]
);
(
//idx 2
//lib.filter.003
[-1],
[]
);
(
//idx 3
//lib.filter.004
[0],
[0]
);
(
//idx 4
//lib.filter.005
[1],
[]
);
(
//IDX5
//lib.filter.006
[1; 2],
[2]
);
(
//idx 6
//lib.filter.007
[1; 3],
[]
);
(
//idx 7
//lib.filter.008
[2; 3],
[2]
);
(
//idx 8
//lib.filter.009
[1; 2; 3],
[2]
);
(
//idx 9
//lib.filter.010
[2; 3; 4],
[2; 4]
);
|]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
[]
让``列表筛选器``idx=
let(list,x)=过滤器值。[idx]
let(u,result)=filterValues。[idx]
List.filter(乐趣x->x%2=0)列表
|>结果应该相等
过滤器(乐趣x->x%2=0)列表
|>结果应该相等

IIRC将NUnit与F#一起使用的问题是要记住在正确的位置使用

一天结束时,我意识到我本来就不应该使用数组

我终于理解了TestCase机制应该如何工作: 它只是将注释的内容作为参数传递给一个函数,该函数现在不再是
unit->unit
,而是(在我的例子中)
string->unit
。因此,我的所有数据项现在都粘贴到单独的TestCase注释中,数组也就消失了。当然,这看起来可能有点奇怪,因为有测试用例注释,其内容跨越许多代码行,但数组也不漂亮,就这样吧

不幸的是,我的解决方案不是普遍适用的,例如,对于上面的Guy Coder的代码不起作用。原因如下:他们说:

CLI对属性参数的种类有限制:

  • 原语:布尔、整型、浮点等
  • 列举
  • 类型引用:System.type
  • “kinda对象”:上面类型的装箱(如果需要)表示
  • 上述类型之一的一维数组(即不允许嵌套数组)
因此,我们可以得出这样的结论:不能将tuple用作 属性参数的类型

在NUnit3中,针对最佳实践,第一部分增加了:


但是,我想知道是否可以避免为数组中的每个条目拼写一行的噪音。想象一下我有一个
namespace NUnit3Demo

open NUnit.Framework
open FsUnit

[<TestFixture>]
module MyTest = 

    let methodToBeTested s = 
        if String.length s > 3 then failwith "Something's wrong"
        else String.length s

    let validData =
        [
            TestCaseData("   ").Returns(3)
            TestCaseData("").Returns(0)
            TestCaseData("a").Returns(1)
        ]

    let invalidData =
        [
            "    "
            "abcd"
            "whatever"
        ]

    let otherInvalidData =
        [
            "just"
            "because"
        ]

    [<TestCaseSource("invalidData");
      TestCaseSource("otherInvalidData")>]
    let ``More than 3 characters throws`` s = 
        (fun () -> methodToBeTested s |> ignore)
        |> should throw typeof<System.Exception>

    [<TestCaseSource("validData")>]
    let ``Less than 4 characters returns length`` s = 
        methodToBeTested s
let validData =
    [
        "   ", 3
        "",    0
        "a",   1
    ] |> List.map (fun (d, r) -> TestCaseData(d).Returns r)