添加到@rmunn 的出色答案:
如果您想测试 myFunc(是的,我还重命名了您的 list 函数),您可以通过创建一些您已经知道答案的固定案例来做到这一点,例如:
let myFunc p = if List.contains " " p || List.contains null p then false else true
let tests =
testList "myFunc" [
testCase "empty list" <| fun()-> "empty" |> Expect.isTrue (myFunc [ ])
testCase "nonempty list" <| fun()-> "hi" |> Expect.isTrue (myFunc [ "hi" ])
testCase "null case" <| fun()-> "null" |> Expect.isFalse (myFunc [ null ])
testCase "empty string" <| fun()-> "\"\"" |> Expect.isFalse (myFunc [ "" ])
]
Tests.runTests config tests
这里我使用了一个名为 Expecto 的测试库。
如果你运行它,你会看到其中一个测试失败:
失败了! myFunc/空字符串:
“”。实际值为真,但预期为假。
因为你原来的函数有错误;它检查空格 " " 而不是空字符串 ""。
修复后所有测试通过:
4 个测试在 00:00:00.0105346 中为 myFunc 运行 – 4 个通过,0 个被忽略,0 个
失败,0 错误。成功!
此时您只检查了 4 个简单而明显的案例,每个案例都有零个或一个元素。很多时候,当输入更复杂的数据时,函数会失败。问题是您可以添加多少测试用例?可能性实际上是无限的!
FsCheck
这就是 FsCheck 可以为您提供帮助的地方。使用 FsCheck,您可以检查应该始终为真的属性(或规则)。想出好东西来测试和认可需要一点点创造力,有时这并不容易。
在您的情况下,我们可以测试连接。规则是这样的:
- 如果连接两个列表,则应用于连接的
MyFunc 的结果应为 true(如果两个列表格式正确)和 false(如果其中任何一个列表格式错误)。
你可以用这种方式把它表达为一个函数:
let myFuncConcatenation l1 l2 = myFunc (l1 @ l2) = (myFunc l1 && myFunc l2)
l1 @ l2 是两个列表的串联。
现在如果你调用 FsCheck:
FsCheck.Verbose myFuncConcatenation
它尝试了 100 种不同的组合,试图让它失败,但最终它给了你成功:
0:
["X"]
["^"; ""]
1:
["C"; ""; "M"]
[]
2:
[""; ""; ""]
[""; null; ""; ""]
3:
...
Ok, passed 100 tests.
这并不一定意味着您的函数是正确的,仍然可能存在 FsCheck 没有尝试的失败组合,或者它可能以不同的方式出错。但这很好地表明它在连接属性方面是正确的。
使用 FsCheck 测试连接属性实际上允许我们使用不同的值调用 myFunc 300 次,并证明它没有崩溃或返回意外值。
FsCheck 不是逐个测试,而是对其进行补充:
请注意,如果您在有错误的原始函数上运行 FsCheck.Verbose myFuncConcatenation,它仍然会通过。原因是该错误与串联属性无关。这意味着您应该始终对最重要的情况进行逐案测试,并且可以使用 FsCheck 对其进行补充以测试其他情况。
您可以检查以下其他属性,这些属性分别测试两个错误条件:
let myFuncHasNulls l = if List.contains null l then myFunc l = false else true
let myFuncHasEmpty l = if List.contains "" l then myFunc l = false else true
Check.Quick myFuncHasNulls
Check.Quick myFuncHasEmpty
// Ok, passed 100 tests.
// Ok, passed 100 tests.