快速检查运行时错误

Quickcheck for runtime errors

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

data Result = A | B

fun :: Result -> Int
fun A = 5

main = check fun

在上面的代码中,我有一个自定义数据类型和一个不完整的函数。如果通过 B,此函数将失败。当然,运行时错误的类型不仅仅是不完整的函数。我想检查生成数据,并将其提供给函数。不关心结果如何。快速检查可以做到这一点吗?

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

而是尽可能确保不处理 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 always 在非空的情况下被调用 列出整个程序,继续使用它。如果不能,请使用模式 匹配(参见 this discussionhead 的类型上)。

无论哪种方式,鉴于旧版本的 GHC 不提供堆栈调用,您宁愿在编译时出现错误,也不愿在运行时出现没有堆栈跟踪的错误(最新版本的 GHC 对此有一些不错的功能) .