使用类型类约束进行快速检查并报告生成的值?
Quickchecking with a typeclass constraint and reporting the generated values?
我正在尝试对国际象棋游戏进行基于 属性 的测试。我设置了以下类型class
class Monad m => HasCheck m where
isCollision :: Coord -> m Bool
检查给定坐标是否包含碰撞或越界。
现在我有一个函数可以为骑士生成允许的动作集,如下所示
collisionKnightRule :: HasCheck m => Coord -> m (Set Coord)
collisionKnightRule =
Set.filterM isCollision . knightMoveSet
-- | Set of all moves, legal or not
knightMoveSet :: Coord -> Set Coord
knightMoveSet (x,y) =
Set.fromList
[ (x+2,y-1),(x+2,y+1),(x-2,y-1),(x-2,y+1)
, (x+1,y-2),(x+1,y+2),(x-1,y-2),(x-1,y+2)
]
knightMoves :: HasCheck m => Coord -> m (Set Coord)
knightMoves pos =
do let moveSet =
knightMoveSet pos
invalidMoves <- collisionKnightRule pos
return $ Set.difference moveSet invalidMoves
以及任意坐标的 HasCheck class 实例
instance HasCheck Gen where
isCollision _ =
Quickcheck.arbitrary
然后为了测试这一点,我想确保生成的移动集是所有可能移动的适当子集。
knightSetProperty :: Piece.HasCheck Gen
=> (Int,Int)
-> Gen Bool
knightSetProperty position =
do moves <- Piece.knightMoves position
return $ moves `Set.isProperSubsetOf` (Piece.knightMoveSet position)
-- ... later on
it "Knight ruleset is subset" $
quickCheck knightSetProperty
当然这会失败,因为这可能是马不能移动到任何地方,这意味着它不是一个适当的子集而是同一个集合。然而,报告的错误并不是特别有用
*** Failed! Falsifiable (after 14 tests and 3 shrinks):
(0,0)
这是因为 quickcheck 不报告 isCollision 的生成值。因此我想知道,如何让 quickCheck 报告 isCollision
的生成值?
好的,所以我觉得这应该可以通过其他方式解决。但是,我提出了以下受 handler pattern.
启发的解决方案
我把HasCheck类型类改成一条记录,如下:
data Handle = MakeHandle
{ isCollision :: Coord -> Bool
}
然后重构所有代码以使用 handle 而不是 HasCheck。
collisionKnightRule :: Handle -> Coord -> (Set Coord)
collisionKnightRule handle =
Set.filter (isCollision handle) . knightMoveSet
-- | Set of all moves, legal or not
knightMoveSet :: Coord -> Set Coord
knightMoveSet (x,y) =
Set.fromList
[ (x+2,y-1),(x+2,y+1),(x-2,y-1),(x-2,y+1)
, (x+1,y-2),(x+1,y+2),(x-1,y-2),(x-1,y+2)
]
-- | Set of illegal moves
knightRuleSet :: Handle -> Coord -> (Set Coord)
knightRuleSet =
collisionKnightRule
knightMoves :: Handle -> Coord -> (Set Coord)
knightMoves handle pos =
let
moveSet =
knightMoveSet pos
invalidMoves =
knightRuleSet handle pos
in
Set.difference moveSet invalidMoves
这样做的缺点是,我担心对于有状态代码,当您传入过时的句柄时很容易引入错误,即 I.E.有多个真相来源。一个优点是,Haskell 的新手可能更容易理解。我们现在可以使用 Quickcheck 的 Function 类型类来模拟函数,并将它们作为参数传递给 mockHandler:
knightSetProperty ::
Fun (Int,Int) Bool
-> (Int,Int)
-> Gen Bool
knightSetProperty (Fun _ isCollision) position =
let
handler =
Piece.MakeHandle isCollision
moveSet =
Piece.knightMoves handler position
in
return $ moveSet `Set.isProperSubsetOf` (Piece.knightMoveSet position)
现在用反例正确地失败了:
*** Failed! Falsifiable (after 53 tests and 74 shrinks):
{_->False}
(0,0)
我正在尝试对国际象棋游戏进行基于 属性 的测试。我设置了以下类型class
class Monad m => HasCheck m where
isCollision :: Coord -> m Bool
检查给定坐标是否包含碰撞或越界。
现在我有一个函数可以为骑士生成允许的动作集,如下所示
collisionKnightRule :: HasCheck m => Coord -> m (Set Coord)
collisionKnightRule =
Set.filterM isCollision . knightMoveSet
-- | Set of all moves, legal or not
knightMoveSet :: Coord -> Set Coord
knightMoveSet (x,y) =
Set.fromList
[ (x+2,y-1),(x+2,y+1),(x-2,y-1),(x-2,y+1)
, (x+1,y-2),(x+1,y+2),(x-1,y-2),(x-1,y+2)
]
knightMoves :: HasCheck m => Coord -> m (Set Coord)
knightMoves pos =
do let moveSet =
knightMoveSet pos
invalidMoves <- collisionKnightRule pos
return $ Set.difference moveSet invalidMoves
以及任意坐标的 HasCheck class 实例
instance HasCheck Gen where
isCollision _ =
Quickcheck.arbitrary
然后为了测试这一点,我想确保生成的移动集是所有可能移动的适当子集。
knightSetProperty :: Piece.HasCheck Gen
=> (Int,Int)
-> Gen Bool
knightSetProperty position =
do moves <- Piece.knightMoves position
return $ moves `Set.isProperSubsetOf` (Piece.knightMoveSet position)
-- ... later on
it "Knight ruleset is subset" $
quickCheck knightSetProperty
当然这会失败,因为这可能是马不能移动到任何地方,这意味着它不是一个适当的子集而是同一个集合。然而,报告的错误并不是特别有用
*** Failed! Falsifiable (after 14 tests and 3 shrinks):
(0,0)
这是因为 quickcheck 不报告 isCollision 的生成值。因此我想知道,如何让 quickCheck 报告 isCollision
的生成值?
好的,所以我觉得这应该可以通过其他方式解决。但是,我提出了以下受 handler pattern.
启发的解决方案我把HasCheck类型类改成一条记录,如下:
data Handle = MakeHandle
{ isCollision :: Coord -> Bool
}
然后重构所有代码以使用 handle 而不是 HasCheck。
collisionKnightRule :: Handle -> Coord -> (Set Coord)
collisionKnightRule handle =
Set.filter (isCollision handle) . knightMoveSet
-- | Set of all moves, legal or not
knightMoveSet :: Coord -> Set Coord
knightMoveSet (x,y) =
Set.fromList
[ (x+2,y-1),(x+2,y+1),(x-2,y-1),(x-2,y+1)
, (x+1,y-2),(x+1,y+2),(x-1,y-2),(x-1,y+2)
]
-- | Set of illegal moves
knightRuleSet :: Handle -> Coord -> (Set Coord)
knightRuleSet =
collisionKnightRule
knightMoves :: Handle -> Coord -> (Set Coord)
knightMoves handle pos =
let
moveSet =
knightMoveSet pos
invalidMoves =
knightRuleSet handle pos
in
Set.difference moveSet invalidMoves
这样做的缺点是,我担心对于有状态代码,当您传入过时的句柄时很容易引入错误,即 I.E.有多个真相来源。一个优点是,Haskell 的新手可能更容易理解。我们现在可以使用 Quickcheck 的 Function 类型类来模拟函数,并将它们作为参数传递给 mockHandler:
knightSetProperty ::
Fun (Int,Int) Bool
-> (Int,Int)
-> Gen Bool
knightSetProperty (Fun _ isCollision) position =
let
handler =
Piece.MakeHandle isCollision
moveSet =
Piece.knightMoves handler position
in
return $ moveSet `Set.isProperSubsetOf` (Piece.knightMoveSet position)
现在用反例正确地失败了:
*** Failed! Falsifiable (after 53 tests and 74 shrinks):
{_->False}
(0,0)