如何测试接收简单类型newtype智能包装器的多态函数?
解决方法
针对你遇到的这种智能包装器类型的QuickCheck测试问题,有以下几种典型的解决思路:
1. 为智能包装器类型定义合法的Arbitrary实例
这是最直接的方案——既然你的函数原本就是针对Foo、Bar这类智能类型设计的,直接给它们实现符合自身约束的Arbitrary实例,从根源上保证生成的测试用例合法。
比如给Foo写实例:
import Test.QuickCheck instance Arbitrary Foo where -- 只生成正整数对应的Foo值 arbitrary = Foo <$> (arbitrary `suchThat` (> 0)) -- 如果想严格使用makeFoo构造(避免直接构造Newtype),可以这样写: -- arbitrary = makeFoo <$> (arbitrary `suchThat` (> 0))
这样测试时直接用Foo类型的测试用例,完全贴合类型的运行时约束,不会出现无效值问题。
2. 用新类型包装合法的基础类型,配合多态签名测试
如果你想保留多态签名的通用性测试,同时避免基础类型(比如Int)生成无效值,可以定义一个带约束的新类型,给它实现合法的Arbitrary实例,再用这个新类型作为多态参数的实例。
示例:
-- 定义只包含正整数的新类型 newtype PositiveInt = PositiveInt Int deriving (Eq, Show) instance Arbitrary PositiveInt where arbitrary = PositiveInt <$> (arbitrary `suchThat` (> 0)) -- 基于这个新类型写测试用例 prop_work :: PositiveInt -> [(PositiveInt, Bar)] -> Bool prop_work (PositiveInt x) xs = let -- 转换为Foo类型(如果需要) foo = makeFoo x fooBarList = map (\(PositiveInt a, b) -> (makeFoo a, b)) xs in -- 这里写你的测试断言,比如验证work的输出符合预期
这种方法既测试了多态函数的通用性,又保证了输入的合法性。
3. 在测试用例中直接使用带约束的生成器
如果不想新增类型或实例,可以直接在测试用例里控制生成器的输出,只生成符合Foo约束的基础类型值,再转换为Foo传入函数。
示例:
import Test.QuickCheck -- 生成符合Foo约束的正整数 genValidFooInt :: Gen Int genValidFooInt = arbitrary `suchThat` (> 0) -- 生成合法的(Foo, Bar)列表 genValidFooBarList :: Gen [(Foo, Bar)] genValidFooBarList = listOf $ (,) <$> (makeFoo <$> genValidFooInt) <*> arbitrary -- 测试用例 prop_work_valid :: Property prop_work_valid = forAll genValidFooInt $ \x -> forAll genValidFooBarList $ \xs -> let foo = makeFoo x in -- 验证work foo xs的输出是否符合预期
这种方式灵活度高,不需要修改现有类型或新增实例,适合临时测试场景。
4. 改造智能构造函数为安全版本,处理无效输入
如果你的代码允许,可以把makeFoo这类构造函数改成返回Maybe类型,避免运行时error,然后在测试中明确处理无效输入的情况。
先修改Foo模块:
module Foo (Foo, makeFoo, int) where newtype Foo = Foo { int :: Int } makeFoo :: Int -> Maybe Foo makeFoo 0 = Nothing makeFoo i = Just (Foo i)
然后写测试用例:
prop_work_safe :: Int -> [(Int, Bar)] -> Property prop_work_safe x xs = case (makeFoo x, traverse (\(a, b) -> (,) <$> makeFoo a <*> pure b) xs) of -- 只测试输入全部合法的情况 (Just foo, Just validList) -> -- 验证work foo validList的输出 -- 无效输入可以选择跳过,或者测试函数的错误处理逻辑(如果有) _ -> property True
这种方案提升了类型安全性,同时能明确区分合法/非法输入的测试场景。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

