快速检查运行时错误 [英] Quickcheck for runtime errors

查看:182
本文介绍了快速检查运行时错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

data Result = A | B

fun :: Result -> Int
fun A = 5

main = check fun

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

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

解决方案

请尽可能确保不处理IO的函数不会引发异常. 只能在IO中捕获异常,它们会破坏您原本希望从纯函数中获得的东西.

有一些来自 Control.Exception Test.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 总是被非空调用 在整个程序中列出来,继续使用它.如果不能,请使用模式 匹配(请参见此讨论 (按head类型).

无论哪种方式,鉴于较旧版本的GHC不提供堆栈调用,您宁愿希望在编译时出错,而不是在运行时没有堆栈跟踪的错误(最近版本的GHC为此提供了一些不错的功能)./p>

I have interest in using the quick check library but it seems that it is designed to test properties. What I would like to do is generate random data for my defined data types and test functions I have written. I do not care about what the result is, just if the function produces a runtime error when fed random data. All of the quick check examples I have seen are for testing properties of functions like is the result greater than 5 when fed random data. Is there a way to use quick check in this manner? Something like

data Result = A | B

fun :: Result -> Int
fun A = 5

main = check fun

In the above code I have a custom data type, and a incomplete function. This function will fail if passed B. There are of course more types of runtime errors than just incomplete functions. I would like to have check generate data, and feed it to the function. Not caring what the result is. Is quick check able to do this?

Edit - I should note that I am not looking for flags that check for incomplete patterns, and what not. I am interested in general purpose runtime error checks.

解决方案

Rather make sure that functions that don't handle IO don't throw exceptions if possible. Exceptions can only get caught in IO, and they kind of break the things you otherwise expect from a pure function.

It's possible with some helpers from Control.Exception and Test.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)

For starters, let's write a function that enables us to check that a certain exception is thrown:

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

This enables you to write tests like this:

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

throwsExceptionOr is very general, so that you can define your own helpers:

> -- | 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

Now you can write your tests as usual:

> 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)

You can go further and move the monadicIO . run into another helper, but that's left as an exercise. Furthermore, you can make the functions compatible with other testing frameworks, such as hspec or tasty:

> 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

That being said, regardless of the used languages you want to get rid of possible runtime errors at compile time if possible. This includes getting rid of partial functions or possible type nonsense. This depends on the actual use, of course. If you can verify that head always gets called on a non-empty list throughout your program, go ahead and use it. If you can't, use pattern matching (see this discussion on head's type).

Either way, given that older versions of GHC don't provide stack calls, you rather want to have errors at compile time than errors without a stack trace at runtime (recent versions of GHC have some nice features for this).

这篇关于快速检查运行时错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆