使用QuickCheck

时间:2016-08-13 07:13:43

标签: haskell testing monads quickcheck

是否有用于测试自定义monad的laws的库或工具?我当前的黑客攻击尝试是这样的:

  • 定义Arbitrary1,类似于Eq1Show1等。
  • 定义一个帮助器类型,将Arbitrary1包裹为Arbitrary
  • 为monadic law定义测试(例如)。

这是否已在某处实施?

{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
import Data.Functor.Classes
import Data.Proxy
import Test.QuickCheck
import Test.QuickCheck.Function
import Test.QuickCheck.Poly

Arbitrary1类型定义* -> *

class Arbitrary1 m where
    arbitrary1 :: (Arbitrary a) => Gen (m a)
    shrink1 :: (Arbitrary a) => m a -> [m a]
    shrink1 _ = []

还有一个辅助包装器,以便我们可以使用与Arbitrary一起使用的函数:

newtype Action m a = Action { getAction :: m a }

instance (Arbitrary a, Arbitrary1 m) => Arbitrary (Action m a) where
    arbitrary = Action <$> arbitrary1
    shrink = map Action . shrink1 . getAction

instance (Show a, Show1 m) => Show (Action m a) where
    showsPrec p = showsPrec1 p . getAction

现在我们可以写一个这样的测试:

-- (m >>= f) >>= g  ≡   m >>= (\x -> f x >>= g)
testBindAssoc :: forall m . (Monad m, Arbitrary1 m, Show1 m, Eq1 m) => Proxy m -> Property
testBindAssoc _ =
    forAllShrink (arbitrary :: Gen (Action m A)) shrink $ \m' ->
    forAllShrink (arbitrary :: Gen (Fun A (Action m B))) shrink $ \f' ->
    forAllShrink (arbitrary :: Gen (Fun B (Action m C))) shrink $ \g' ->
    let m = getAction m'
        f = getAction <$> apply f'
        g = getAction <$> apply g'
        k = (m >>= f) >>= g
        l = m >>= (\x -> f x >>= g)
    in counterexample (showsPrec1 0 k . showString " != " . showsPrec1 0 l $ "")
        $ k `eq1` l

让我们写一个破碎的Writer monad:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.Writer

newtype MyMonad w a = MyMonad { runMyMonad :: Writer w a }
  deriving (Functor, Applicative)

instance (Monoid w) => Monad (MyMonad w) where
    return = pure
    k >>= f = let (a, w) = runWriter . runMyMonad $ k
              in MyMonad $ writer (a, w <> w) >>= (runMyMonad . f)
--                                    ^ broken here

getMyMonad :: MyMonad w a -> (a, w)
getMyMonad = runWriter . runMyMonad

instance (Eq w) => Eq1 (MyMonad w) where
    eq1 k l = getMyMonad k == getMyMonad l

instance (Show w) => Show1 (MyMonad w) where
    showsPrec1 p k = showsPrec p (getMyMonad k)

instance (Monoid w, Arbitrary w) => Arbitrary1 (MyMonad w) where
    arbitrary1 = MyMonad . writer <$> arbitrary
    shrink1 = map (MyMonad . writer) . shrink . getMyMonad

main :: IO ()
main = quickCheck (testBindAssoc (Proxy :: Proxy (MyMonad (Sum Int))))

失败:

*** Failed! Falsifiable (after 2 tests and 13 shrinks):   
(1,Sum {getSum = 1})
{_->(1,Sum {getSum = 0})}
{_->(1,Sum {getSum = 0})}
(1,Sum {getSum = 4}) != (1,Sum {getSum = 2})

有任何改进的想法吗?

0 个答案:

没有答案