【问题标题】:Using QuickCheck to test intentional error conditions使用 QuickCheck 测试故意错误条件
【发布时间】:2013-09-01 18:48:52
【问题描述】:

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

【问题讨论】:

    标签: haskell quickcheck


    【解决方案1】:

    首先免责声明:我不是 QuickCheck 方面的专家,在您提出问题之前我也没有单子检查的经验,但我认为 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. 我发现无法从protect 可以处理的AnException 中提取ExitCode 异常。因此,所有退出代码在这里都被视为相同(它们被映射到Nothing)。我本来希望有:

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

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

      那你会怎么测试呢?

    我也希望得到更多专家的回答。

    【讨论】:

      【解决方案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):
      

      【讨论】:

      • 对,但这似乎预计会出现各种故障。我们可以隔离exitWith 调用产生的那些吗?我们可以断言失败代码是什么吗?
      • @nickie 如果您想测试有关退出代码的属性,您可以为错误处理本身添加一些额外的管道。 IE。有一个exitWith 调用的determineCode :: Either a b -&gt; ExitCode 函数,并对它的行为做出断言。不知道能不能走得更远。
      • expectFailure 似乎在任何测试失败时都通过了,而不是如果所有测试都失败了,这正是我想要的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-25
      相关资源
      最近更新 更多