Haskell 快速检查顺序映射密钥生成

Haskell 快速检查顺序映射密钥生成,haskell,quickcheck,Haskell,Quickcheck,我正在尝试测试自定义数据类型的逻辑。它接收一个Map Int字符串作为参数,然后我需要在对象内部的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

我正在尝试测试自定义数据类型的逻辑。它接收一个Map Int字符串作为参数,然后我需要在对象内部的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 }
import qualified Data.IntMap.Strict作为M
导入数据.UUID(UUID)
进口控制单体状态
导入系统。随机
类型StrMap=M.IntMap字符串
类型MType=状态StdGen
数据MyType=MyType{
uuid::uuid,
strs::StrMap
}派生(显示)
创建::StrMap->MType MyType
创建pm=do
状态Int
STRSIZE e=M.size$strs e
addStr::MyType->String->MyType
addStr e p=e{strs=M.insert(strsize e)p$strs e}
在映射中具有顺序键很重要,因此不接受具有[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
main::IO()
主=hspec规格
规格:规格
spec=描述“创建和更新”$do
QuickCheck.prop“检查地图添加”$do
\xs str->monadicIO$do
state not(null xs)属性==>monadicIO$do

state代替使用
QuickCheck
生成满足某些复杂不变量的任意数据(这可能很困难),您可以使用QuickCheck生成完全任意的数据,然后从中构造满足不变量的数据(通过您相信正确的测试系统外部的某种方法)

这种情况下的不变量表示为“键必须是连续的”,但实际上是“键必须是连续的并且从0开始”。这是足够的,但超出了必要的范围。
addStr
要求的最小不变量是“映射不能包含与映射大小相同的键”,因为这是我们要插入的键。通过简化约束,我们也使其更容易满足:我们可以生成任意映射(其中可能包含坏键),然后删除坏键,得到满意的映射

我还将注意到UUID(以及生成UUID的机制,它需要
状态
,可能还需要
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
创建一个
arbitral
实例,执行类似操作,或者满足更强不变量的操作(其他测试可能需要)。我将把它作为一个练习留给你,但是如果你在尝试它时遇到困难,请随意问更多的问题。

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