我如何测试某些东西对地图中的所有元素都有效?
How do I test that something is valid for all elements in a map?
我的申请中有以下内容:
newtype User = User Text
newtype Counts = Counts (Map User Int)
subjectUnderTest :: Counts -> Text
正确输出的示例是
> subjectUnderTest $ fromList [(User "foo", 4), (User "bar", 4), (User "qux", 2)]
"4: foo, bar\n2: qux"
我想编写基于 属性 的测试来验证诸如“所有用户都在输出中表示”、“所有计数都在输出中表示”和“所有用户都在同一行上”之类的东西作为他们相应的计数”。这些属性的共同点是它们的措辞以“all ...”开头。
如何编写一个 属性 来验证某些内容对 Map
中的每个元素都有效?
我假设这个问题只是更复杂问题的简化表示,所以这里有一些策略需要考虑:
拆分功能
看起来 subjectUnderTest
做了两件不相关的事情:
- 它按值而不是键对映射中的值进行分组。
- 它格式化,或 pretty-prints,倒置地图。
如果您可以将功能拆分为这两个步骤,则它们更容易单独测试。
第一步,可以进行参数化多态。与其测试类型为 Counts -> Text
的函数,不如考虑测试类型为 Eq b => Map a b -> [(b, [a])]
的函数。 Property-based 使用参数多态性测试更容易,因为 you get certain properties for free。例如,您可以确定输出中的值只能来自输入,因为无法凭空变出 a
和 b
值。
您仍然需要为您询问的属性编写测试。编写一个类型为 Eq b => Map a b -> Testable
的函数。如果您想测试所有值是否都存在,请将它们从地图中拉出并列出它们。对列表进行排序并 nub
它。它现在是 [b]
值。这是您的预期输出。
现在调用你的函数。它 returns 类似于 [(b, [a])]
。使用 fst
对其进行映射,对其进行排序并 nub
。该列表应该等于您的预期输出。
下一步 (pretty-printing),请参阅下一节。
往返
当你想要property-basepretty-printing时,最简单的方法通常是硬着头皮写一个解析器。打印机和解析器应该是彼此的对偶,所以如果你有一个函数 MyType -> String
,你应该有一个类型为 String -> Maybe MyType
.
的解析器
您现在可以像 MyType -> Testable
这样写一个通用的 属性。它以 MyType
的值作为输入(我们称它为 expected
)。您现在生成一个值(我们称之为 actual
)作为 actual = parse $ print expected
。您现在可以验证 Just expected === actual
.
如果特定的 String
格式很重要,我会使用 good old parametrised tests.
用几个实际的例子来跟进它
仅仅因为您正在进行 property-based 测试并不意味着 'normal' 单元测试也没有用。
例子
这是我上面的意思的一个简单例子。假设
invertMap :: (Ord b, Eq b) => Map a b -> [(b, [a])]
您可以将属性之一定义为:
allValuesAreNowKeys :: (Show a, Ord a) => Map k a -> Property
allValuesAreNowKeys m =
let expected = nub $ sort $ Map.elems m
actual = invertMap m
in expected === nub (sort $ fmap fst actual)
由于这个 属性 仍然是参数多态的,您必须将它添加到具有特定类型的测试套件中,例如:
tests = [
testGroup "Sorting Group 1" [
testProperty "all values are now keys" (allValuesAreNowKeys :: Map String Int -> Property)]]
有更漂亮的方法来定义属性列表;那个只是 quickcheck-test-framework 堆栈模板使用的模板...
我的申请中有以下内容:
newtype User = User Text
newtype Counts = Counts (Map User Int)
subjectUnderTest :: Counts -> Text
正确输出的示例是
> subjectUnderTest $ fromList [(User "foo", 4), (User "bar", 4), (User "qux", 2)]
"4: foo, bar\n2: qux"
我想编写基于 属性 的测试来验证诸如“所有用户都在输出中表示”、“所有计数都在输出中表示”和“所有用户都在同一行上”之类的东西作为他们相应的计数”。这些属性的共同点是它们的措辞以“all ...”开头。
如何编写一个 属性 来验证某些内容对 Map
中的每个元素都有效?
我假设这个问题只是更复杂问题的简化表示,所以这里有一些策略需要考虑:
拆分功能
看起来 subjectUnderTest
做了两件不相关的事情:
- 它按值而不是键对映射中的值进行分组。
- 它格式化,或 pretty-prints,倒置地图。
如果您可以将功能拆分为这两个步骤,则它们更容易单独测试。
第一步,可以进行参数化多态。与其测试类型为 Counts -> Text
的函数,不如考虑测试类型为 Eq b => Map a b -> [(b, [a])]
的函数。 Property-based 使用参数多态性测试更容易,因为 you get certain properties for free。例如,您可以确定输出中的值只能来自输入,因为无法凭空变出 a
和 b
值。
您仍然需要为您询问的属性编写测试。编写一个类型为 Eq b => Map a b -> Testable
的函数。如果您想测试所有值是否都存在,请将它们从地图中拉出并列出它们。对列表进行排序并 nub
它。它现在是 [b]
值。这是您的预期输出。
现在调用你的函数。它 returns 类似于 [(b, [a])]
。使用 fst
对其进行映射,对其进行排序并 nub
。该列表应该等于您的预期输出。
下一步 (pretty-printing),请参阅下一节。
往返
当你想要property-basepretty-printing时,最简单的方法通常是硬着头皮写一个解析器。打印机和解析器应该是彼此的对偶,所以如果你有一个函数 MyType -> String
,你应该有一个类型为 String -> Maybe MyType
.
您现在可以像 MyType -> Testable
这样写一个通用的 属性。它以 MyType
的值作为输入(我们称它为 expected
)。您现在生成一个值(我们称之为 actual
)作为 actual = parse $ print expected
。您现在可以验证 Just expected === actual
.
如果特定的 String
格式很重要,我会使用 good old parametrised tests.
仅仅因为您正在进行 property-based 测试并不意味着 'normal' 单元测试也没有用。
例子
这是我上面的意思的一个简单例子。假设
invertMap :: (Ord b, Eq b) => Map a b -> [(b, [a])]
您可以将属性之一定义为:
allValuesAreNowKeys :: (Show a, Ord a) => Map k a -> Property
allValuesAreNowKeys m =
let expected = nub $ sort $ Map.elems m
actual = invertMap m
in expected === nub (sort $ fmap fst actual)
由于这个 属性 仍然是参数多态的,您必须将它添加到具有特定类型的测试套件中,例如:
tests = [
testGroup "Sorting Group 1" [
testProperty "all values are now keys" (allValuesAreNowKeys :: Map String Int -> Property)]]
有更漂亮的方法来定义属性列表;那个只是 quickcheck-test-framework 堆栈模板使用的模板...