【问题标题】:Find the value that failed for quickcheck查找快速检查失败的值
【发布时间】:2026-01-25 13:30:01
【问题描述】:

当某个值未能通过 QuickCheck 测试时,我想将其用于调试。有什么办法可以做类似的事情:

let failValue = quickCheck' myTest
in someStuff failValue

如果我的数据是readable,那么我可能会破解一些方法从 IO 获取它,但事实并非如此。

【问题讨论】:

    标签: haskell quickcheck


    【解决方案1】:

    我在 QuickCheck API 中找不到任何可以很好地做到这一点的东西,但这是我使用 monadic QuickCheck API 拼凑而成的。它拦截并在IORef 中记录到您的属性的输入,并假设如果失败,则最后一个是罪魁祸首,并将其返回到Just。如果测试通过,则结果为Nothing。这可能可以稍微改进一下,但对于简单的单参数属性,它应该可以完成这项工作。

    import Control.Monad
    import Data.IORef
    import Test.QuickCheck
    import Test.QuickCheck.Monadic
    
    prop_failIfZero :: Int -> Bool
    prop_failIfZero n = n /= 0
    
    quickCheck' :: (Arbitrary a, Show a) => (a -> Bool) -> IO (Maybe a)
    quickCheck' prop = do input <- newIORef Nothing
                          result <- quickCheckWithResult args (logInput input prop)
                          case result of
                             Failure {} -> readIORef input
                             _ -> return Nothing
      where
        logInput input prop x = monadicIO $ do run $ writeIORef input (Just x)
                                               assert (prop x)
        args = stdArgs { chatty = False }
    
    main = do failed <- quickCheck' prop_failIfZero
              case failed of
                  Just x -> putStrLn $ "The input that failed was: " ++ show x
                  Nothing -> putStrLn "The test passed"
    

    【讨论】:

    • 这个小技巧让我的 Haskell 调试体验好了很多。谢谢
    • 我为此苦苦挣扎了一段时间,那篇文章对我来说非常有用,可以作为思想的食物,非常感谢。我想我也会在这里放一个指向whenFail(和wnehFail')(hackage.haskell.org/package/QuickCheck-2.13.2/docs/…)的指针,它需要IO ()在失败时执行,最终成为我使用的。
    【解决方案2】:

    一种方法是使用sample' 方法,手动运行测试并找出失败的值。例如,测试一个错误的双重功能:

    import Test.QuickCheck
    
    double :: Int -> Int
    double x | x < 10 = 2 * x
             | otherwise = 13
    
    doubleTest :: Int -> Bool
    doubleTest x = x + x == double x
    
    tester :: IO ()
    tester = do
      values <- sample' arbitrary
      let failedValues = filter (not . doubleTest) values
      print failedValues
    

    唯一的问题是sample'只生成了11个测试值,可能不足以触发bug。

    【讨论】: