sam*_*ces 8 javascript testing functional-programming typescript
假设您有一个采用联合类型的函数,然后将该类型缩小并委托给其他两个纯函数之一。
function foo(arg: string|number) {
if (typeof arg === 'string') {
return fnForString(arg)
} else {
return fnForNumber(arg)
}
}
Run Code Online (Sandbox Code Playgroud)
假定fnForString()和fnForNumber()也是纯函数,并且它们本身已经过测试。
应该如何进行测试foo()?
fnForString(),并fnForNumber()作为一个实现细节,而且基本上编写测试时,重复测试,他们每个人的foo()?这种重复是否可以接受?foo()授人以fnForString()和fnForNumber()如通过嘲笑出来,并检查其委托给他们呢?在理想的世界中,您会编写证明而不是测试。例如,考虑以下函数。
\n\nconst negate = (x: number): number => -x;\n\nconst reverse = (x: string): string => x.split("").reverse().join("");\n\nconst transform = (x: number|string): number|string => {\n switch (typeof x) {\n case "number": return negate(x);\n case "string": return reverse(x);\n }\n};\nRun Code Online (Sandbox Code Playgroud)\n\n假设您想证明transform应用两次是幂等的,即对于所有有效输入x,transform(transform(x))等于x。好吧,您首先需要证明negate和reverse应用两次是幂等的。现在,假设证明negate和reverse应用两次的幂等性是微不足道的,即编译器可以计算出来。因此,我们有以下引理。
const negateNegateIdempotent = (x: number): negate(negate(x))\xe2\x89\xa1x => refl;\n\nconst reverseReverseIdempotent = (x: string): reverse(reverse(x))\xe2\x89\xa1x => refl;\nRun Code Online (Sandbox Code Playgroud)\n\n我们可以使用这两个引理来证明它transform是幂等的,如下所示。
const transformTransformIdempotent = (x: number|string): transform(transform(x))\xe2\x89\xa1x => {\n switch (typeof x) {\n case "number": return negateNegateIdempotent(x);\n case "string": return reverseReverseIdempotent(x);\n }\n};\nRun Code Online (Sandbox Code Playgroud)\n\n这里发生了很多事情,所以让我们来分解一下。
\n\na|b联合类型和a&b交集类型一样,a\xe2\x89\xa1b相等类型也是如此。x相等类型的值是和a\xe2\x89\xa1b相等的证明。aba和b不相等,则无法构造 类型的值a\xe2\x89\xa1b。refl的缩写,具有类型。这是一个值等于其自身的简单证明。a\xe2\x89\xa1arefl的证明中使用了。这是可能的,因为命题对于编译器来说足够简单,可以自动证明。negateNegateIdempotentreverseReverseIdempotentnegateNegateIdempotent和reverseReverseIdempotent引理来证明transformTransformIdempotent。这是一个重要证明的例子。编写证明的优点是编译器可以验证证明。如果证明不正确,则程序无法进行类型检查,并且编译器会抛出错误。证明比测试更好有两个原因。首先,您不必创建测试数据。创建处理所有边缘情况的测试数据很困难。其次,您不会意外地忘记测试任何边缘情况。如果这样做,编译器将抛出错误。
\n\n不幸的是,TypeScript 没有相等类型,因为它不支持依赖类型,即依赖于值的类型。因此,您无法用 TypeScript 编写证明。您可以使用Agda等依赖类型函数编程语言编写证明。
\n\n但是,您可以用 TypeScript 编写命题。
\n\nconst negateNegateIdempotent = (x: number): boolean => negate(negate(x)) === x;\n\nconst reverseReverseIdempotent = (x: string): boolean => reverse(reverse(x)) === x;\n\nconst transformTransformIdempotent = (x: number|string): boolean => {\n switch (typeof x) {\n case "number": return negateNegateIdempotent(x);\n case "string": return reverseReverseIdempotent(x);\n }\n};\nRun Code Online (Sandbox Code Playgroud)\n\n然后,您可以使用jsverify等库自动生成多个测试用例的测试数据。
\n\nconst jsc = require("jsverify");\n\njsc.assert(jsc.forall("number", transformTransformIdempotent)); // OK, passed 100 tests\n\njsc.assert(jsc.forall("string", transformTransformIdempotent)); // OK, passed 100 tests\nRun Code Online (Sandbox Code Playgroud)\n\n您也可以致电jsc.forall,"number | string"但我似乎无法让它工作。
所以来回答你的问题。
\n\n\n\n\n应该如何进行测试
\nfoo()?
函数式编程鼓励基于属性的测试。例如,我测试了两次应用的negate、reverse和transform函数的幂等性。如果您遵循基于属性的测试,那么您的命题函数在结构上应该与您正在测试的函数类似。
\n\n\n\n
fnForString()您是否应该将它委托给和 的事实fnForNumber()视为实现细节,并在为 编写测试时本质上重复每个测试的测试foo()?这种重复可以接受吗?
是的,可以接受吗?尽管如此,您可以完全放弃测试fnForString,fnForNumber因为这些测试包含在foo. 然而,为了完整性,我建议包括所有测试,即使它引入了冗余。
\n\n\n\n
foo()您是否应该编写“知道”委托的测试,fnForString()例如fnForNumber()通过模拟它们并检查它是否委托给它们?
您在基于属性的测试中编写的命题遵循您正在测试的函数的结构。因此,他们通过使用正在测试的其他函数的命题来“了解”依赖关系。没必要嘲笑他们。您只需要模拟网络调用、文件系统调用等。
\n| 归档时间: |
|
| 查看次数: |
175 次 |
| 最近记录: |