【问题标题】:QuickCheck sequential Map key generationQuickCheck 顺序映射键生成
【发布时间】:2020-01-06 09:51:15
【问题描述】:

我正在尝试测试自定义数据类型的逻辑。它接收一个 Map Int String 作为参数,然后我需要在对象内的 Map 中添加一个元素。

类型声明和插入函数如下所示:

import qualified Data.IntMap.Strict as M
import Data.UUID (UUID)
import Control.Monad.State
import System.Random

type StrMap = M.IntMap String
type MType = State StdGen

data MyType = MyType {
    uuid :: UUID,
    strs :: StrMap
} deriving (Show)

create :: StrMap -> MType MyType
create pm = do
    state <- get
    let (uuid, newState) = random state
    put newState
    return $ MyType uuid pm

strsSize :: MyType -> Int
strsSize e = M.size $ strs e

addStr :: MyType -> String -> MyType
addStr e p = e { strs = M.insert (strsSize e) p $ strs e }

在 Map 中具有顺序键很重要,因此具有 [0, 1, 3] 是不可接受的。 我正在尝试使用 HSpec 和 QuickCheck 对其进行测试:

main :: IO ()
main = hspec spec

spec :: Spec
spec = describe "Creation and update" $ do
    QuickCheck.prop "Check map addition" $ do
        \xs str -> monadicIO $ do
            state <- run(getStdGen)
            let (result, newState) = runState (create xs) state
            run(setStdGen newState)
            let result' = addStr result str
            assert $ (strsSize result) + 1 == strsSize result' -- fails here

问题是 QuickCheck 生成随机键,我不确定如何强制它为 Map 生成顺序键。缺少序列的问题是函数 addStr 可能会在重复键的情况下覆盖值,这是不可取的行为。


更新

感谢大家的帮助!经过长时间的讨论和某种思考,我最终得到了以下解决方案:

spec :: Spec
spec = describe "Creation and update" $ do
    QuickCheck.prop "Check map addition" $ do
        \xs str -> not (null xs) Property.==> monadicIO $ do
            state <- run(getStdGen)
            let mp = M.fromList $ zip [0..(length xs)] xs
            let (result, newState) = runState (create mp) state
            run(setStdGen newState)
            let result' = addStr result str
            assert $ (strsSize result) + 1 == strsSize result'

基本上,我必须生成一些随机字符串集,然后手动将它们转换为地图。它可能不是最优雅的解决方案,但它可以根据需要工作。

【问题讨论】:

  • 当您说 QuickCheck 正在生成“随机密钥”时,您的意思是它正在生成随机的StrMaps?这个问题我很难理解。
  • 这不是一个最小可重现示例,所以我不能确定,但​​(M.size . strs result) 看起来像一个类型错误。如果您的示例代码无法编译,就很难回答这样的问题。
  • 它正在生成一个地图,其内容如下:{(0, "xyz"), (3, "qwerty")} 或只是 {(5, "asd")}。就我而言,我需要确保键始终从零开始,并且它们之间没有间隙,即键应始终为 0,1,2,3,4,...
  • 我认为这根本不是您需要的。我看不出这与您正在检查的财产有什么关系。您永远不会以任何方式实际测试 键的值,只测试连续映射的相对大小。
  • 当然你可以澄清它是否编译。这是您的代码,您可以尝试编译它。 addStr 是类型错误我错了,但我不得不猜测这些事情,因为您提供的代码不完整,根本无法编译。

标签: haskell quickcheck


【解决方案1】:

除了使用QuickCheck 生成满足某些复杂不变量的任意数据(这可能很困难)之外,您可以使用 QuickCheck 生成完全任意的数据,然后您可以从中构造满足不变量的数据(通过某些外部方法您认为正确的正在测试的系统)。

这种情况下的不变量是“keys must be contiguous”,但实际上是“keys must be contiguous并且从0开始”。这已经足够了,但超过了必要。 addStr 要求的最小不变量是“映射不能包含映射大小的键”,因为这是我们打算插入的键。通过简化约束,我们也更容易满足:我们可以生成任意映射(可能包含坏键),然后删除坏键,得到满意的映射。

我还要注意,UUID(以及生成它的机制,它需要State 可能还有IO)与被测试的属性无关。这意味着我们可以用我们周围的任何UUID 构造MyType(就像包提供的nil UUID)并避免单子的东西:

spec :: Spec
spec = describe "Creation and update" $ do
  QuickCheck.prop "Check map addition" $ do
    \strmap -> -- we don't actually care what the String being inserted is for this test
      let myType = MyType UUID.nil (M.delete (M.size strmap) strmap) -- Enforce the invariant
      in assert $ strsSize (addStr myType "") = strsSize myType + 1

如果您愿意,您还可以为 MyType 创建一个 Arbitrary 实例,它执行类似的操作,或者满足更强的不变量(其他测试可能需要)。我将把它作为练习留给你,但如果你在尝试时遇到困难,请随时提出更多问题。

【讨论】:

    猜你喜欢
    • 2015-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多