我正在尝试使用 NUnit 为 F# 项目设置测试套件。似乎尤其是在测试解析器和类型检查器之类的东西时,通常会有一个有效输入数据列表和一个无效数据列表。测试本身实际上是相同的,因此我正在寻找一种巧妙的方法来避免为每个数据项编写测试函数,而是将测试函数与数据分开。显然,似乎有一些称为测试用例的东西,但我很难找到关于在F# 中使用 NUnit 3的综合文档,特别是我的场景的最佳实践示例。
任何指针和提示都非常感谢!
这是 NUnit 3.x 的更新答案,因为我的原始答案显示了 NUnit 2.x 示例。
下面的示例并不全面,但足以让您超越门槛并开始跑步。需要注意的是,测试函数是使用参数列表而不是柯里化编写的。还有几种方法可以使用 NUnit 3.x 属性生成测试数据,例如Pairwise,但遗憾的是,没有可用的属性知道如何为有区别的联合生成测试数据。
此外,不需要 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)
留下原始答案,因为它在 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
IIRC 将 NUnit 与 F# 一起使用的问题是要记住<>
在正确的位置使用。
在 NUnit3 中TestCaseSource
,TestCaseData
我添加了FsUnit的最佳实践部分:
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
请注意,TestCaseData
可以获取和返回任意对象(显然它们应该与测试签名匹配)。此外,数据可以写得更好:
let validData =
[
" ", 3
"", 0
"a", 1
] |> List.map (fun (d, r) -> TestCaseData(d).Returns r)
到最后,我意识到我一开始就不应该使用数组!
我终于明白了 TestCase 机制应该如何工作:它只是将注释的内容作为参数传递给现在unit->unit
不再是(在我的情况下)的函数string->unit
。因此,我所有的数据项现在都粘贴到单独的 TestCase 注释中,并且数组消失了。当然,这可能看起来有点奇怪,让 TestCase 注释的内容跨越多行代码但数组也不漂亮,就这样吧。
不幸的是,我的解决方案并不是普遍适用的,例如上面的 Guy Coder 的代码不适用。原因在这里指出:https ://stackoverflow.com/a/28015585/2289899他们说:
CLI 对属性参数的种类有限制:
- 原语:bool、int、float 等
- 枚举
- 字符串
- 类型参考:System.Type
- 'kinda objects':来自上面的类型的盒装(如果需要)表示
- 上面一种类型的一维数组(即不允许嵌套数组)
所以我们可以在这一点上得出结论,你不能使用元组作为属性参数的类型。