使用QuickCheck测试故意错误情况

时间:2013-09-01 18:48:53

标签: haskell quickcheck

我已经看到如何使用QuickCheck来测试monadic和非monadic代码,但是如何使用它来测试处理错误的代码,即打印一些消息然后调用exitWith

2 个答案:

答案 0 :(得分:4)

首先是免责声明:我不是QuickCheck的专家,在你的问题之前我没有使用monadic检查的经验,但我认为stackoverflow是学习新事物的机会。如果有专家的回答说这可以做得更好,我会删除我的。

假设您有一个函数test可以使用exitWith抛出异常。这是我认为你可以测试它的方式。关键函数是protect,它捕获异常并将其转换为可以测试的内容。

import System.Exit
import Test.QuickCheck
import Test.QuickCheck.Property
import Test.QuickCheck.Monadic

test :: Int -> IO Int
test n | n > 100   = do exitWith $ ExitFailure 1
       | otherwise = do print n
                        return n

purifyException :: (a -> IO b) -> a -> IO (Maybe b)
purifyException f x = protect (const Nothing) $ return . Just =<< f x

testProp :: Property
testProp = monadicIO $ do
  input <- pick arbitrary
  result <- run $ purifyException test $ input
  assert $ if input <= 100 then result == Just input
                           else result == Nothing

据我所知,这有两个缺点,但我找不到它们。

  1. 我发现无法从ExitCode可以处理的AnException中提取protect例外。因此,所有退出代码在此处理相同(它们映射到Nothing)。我本来想拥有:

    purifyException :: (a -> IO b) -> a -> IO (Either a ExitCode)
    
  2. 我发现无法测试测试的I / O行为。假设test是:

    test :: IO ()
    test = do
      n <- readLn
      if n > 100 then exitWith $ ExitFailure 1
                 else print n
    

    那你将如何测试呢?

  3. 我也很欣赏更专业的答案。

答案 1 :(得分:2)

QuickCheck expectFailure函数可用于处理此类事物。采用这个简单(并且不推荐)的错误处理框架:

import System.Exit
import Test.QuickCheck
import Test.QuickCheck.Monadic

handle :: Either a b -> IO b
handle (Left _)  = putStrLn "exception!" >> exitWith (ExitFailure 1)
handle (Right x) = return x

并制作了几个虚拟函数:

positive :: Int -> Either String Int
positive x | x > 0     = Right x
           | otherwise = Left "not positive"

negative :: Int -> Either String Int
negative x | x < 0     = Right x
           | otherwise = Left "not negative"

现在我们可以测试错误处理的一些属性。首先,Right值不应导致异常:

prop_returnsHandledProperly (Positive x) = monadicIO $ do
  noErr <- run $ handle (positive x)
  assert $ noErr == x

-- Main*> quickCheck prop_returnsHandledProperly
-- +++ OK, passed 100 tests.

Lefts 应该导致异常。请注意expectFailure已添加到开头:

prop_handlesExitProperly (Positive x) = expectFailure . monadicIO $
  run $ handle (negative x)

-- Main*> quickCheck prop_handlesExitProperly
-- +++ OK, failed as expected. Exception: 'exitWith: invalid argument (ExitFailure 0)' (after 1 test):