快速检查运行时错误

时间:2016-02-23 22:41:20

标签: haskell automation quickcheck

我有兴趣使用快速检查库,但它似乎是为了测试属性而设计的。我想要做的是为我定义的数据类型和我编写的测试函数生成随机数据。我不关心结果是什么,只是在输入随机数据时函数产生运行时错误。我看到的所有快速检查示例都用于测试函数的属性,例如,在输入随机数据时结果大于5。有没有办法以这种方式使用快速检查?像

这样的东西
data Result = A | B

fun :: Result -> Int
fun A = 5

main = check fun

在上面的代码中,我有一个自定义数据类型和一个不完整的函数。如果传递B,该函数将失败。当然,运行错误的类型多于不完整的函数。我想检查生成数据,并将其提供给函数。不关心结果是什么。快速检查是否能够做到这一点?

编辑 - 我应该注意到,我不是在寻找检查不完整模式的标志,而不是。我对通用运行时错误检查感兴趣。

1 个答案:

答案 0 :(得分:2)

确保不会处理IO的函数在可能的情况下不会抛出异常。 异常只能在IO中被捕获,并且它们会破坏您对纯函数所期望的内容。

可能有一些助手来自 Control.ExceptionTest.QuickCheck.Monadic

> import           Control.Exception       (Exception, PatternMatchFail,
>                                           SomeException, evaluate,
>                                           fromException, try)
> import           Data.Either             (either)
> import           Test.Hspec              (anyException, describe, hspec, it,
>                                           shouldThrow)
> import           Test.QuickCheck         hiding (Result)
> import           Test.QuickCheck.Monadic (assert, monadicIO, run)

对于初学者,让我们编写一个函数,使我们能够检查是否抛出了某个异常:

> throwsExceptionOr :: Exception e => (e -> Bool) -> (a -> Bool) -> a -> IO Bool
> throwsExceptionOr pe pa = fmap (either pe pa) . try . evaluate

这使您可以编写如下测试:

> prop_fun_1 x = monadicIO . run $
>   throwsExceptionOr (const True :: SomeException -> Bool)
>                     (== 5)
>                     (foo x)

throwsExceptionOr非常通用,因此您可以定义自己的帮助程序:

> -- | Should always throw an exception.
> throwsException :: Exception e => (e -> Bool) -> a -> IO Bool
> throwsException p = fmap (either p (const False)) . try . evaluate

> -- | Should either pass the test or throw an exception.
> exceptionOr :: a -> (a -> Bool) -> IO Bool
> exceptionOr x p = fmap (either anyException p) . try . evaluate $ x
>   where
>     anyException :: SomeException -> Bool
>     anyException = const True

现在你可以像往常一样编写测试:

> data Result = A | B deriving (Enum, Show, Eq, Ord, Bounded, Read)
> 
> instance Arbitrary Result where
>   arbitrary = elements [A, B]
> 
> foo :: Result -> Int
> foo A = 5
> 
> prop_foo x = monadicIO . run $ foo x `exceptionOr` (== 5)

你可以更进一步,将monadicIO . run移动到另一个助手,但是 那是作为练习留下的。此外,您可以使功能兼容 与其他测试框架,例如hspectasty

> main :: IO ()
> main = hspec $ do
>   describe "foo" $ do
>     it "either returns five or throws a pattern match fail" $ propertyIO $ \x ->
>       throwsExceptionOr patternMatchFail (==5) (foo x)
> 
>     it "throws an exception on input B" $
>       evaluate (foo B) `shouldThrow` anyException
> 
>  where
>   patternMatchFail :: PatternMatchFail -> Bool
>   patternMatchFail _ = True
> 
>   -- I think there is a better combinator for this
>   propertyIO f = property $ \x -> monadicIO . run $ f x

话虽如此,无论使用哪种语言,你都想摆脱 如果可能,编译时可能存在运行时错误。这包括获得 摆脱部分功能或可能的类型废话。这取决于实际情况 当然是用。如果您可以验证是否在非空的情况下调用head 始终 在整个程序中列出,继续使用它。如果你不能,请使用模式 匹配(见this discussion  head的类型)。

无论哪种方式,考虑到旧版本的GHC不提供堆栈调用,您宁愿在编译时遇到错误,而不是在运行时没有堆栈跟踪的错误(最近版本的GHC有一些很好的功能)