haskell - 使用类型类约束进行快速检查并报告生成的值?

标签 haskell testing typeclass quickcheck

我正在尝试对国际象棋游戏进行基于属性的测试。我已经设置了以下类型类

class Monad m => HasCheck m where                                                   
    isCollision :: Coord -> m Bool                                                  

检查给定坐标是否包含碰撞或越界。

现在我有一个函数可以为骑士生成允许的 Action 集,如下所示

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 类实例

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)

关于haskell - 使用类型类约束进行快速检查并报告生成的值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55332047/

相关文章:

list - 如何通过列表理解计算频率?

ruby-on-rails - Rails 中的 Rspec 测试突然开始失败

haskell - 为什么 Haskell 中的这种类型注释是必要的?

haskell - 为什么foldl1'没有推广到Foldable?

Haskell:在类型类中定义变量

haskell - 在 Haskell 中处理涉及 CmpNat 和单例的证明

haskell - Cabal Vs runhaskell,什么时候使用?

matlab - 测试性能分类器matlab

testing - Junit4 : Running a Suite of particular Test methods

F# 类型约束和重载解析