QuickCheck 顺序映射键生成
QuickCheck sequential Map key generation
我正在尝试测试自定义数据类型的逻辑。它接收一个 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 生成随机键,我不确定如何强制它为地图生成顺序键。缺少序列的问题是函数 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 生成完全任意的数据,然后从中构建满足不变量的数据(通过正在测试的系统外部的一些您认为正确的方法。
这种情况下的不变量给出为 "keys must be contiguous",但实际上是 "keys must be contiguous and start from 0"。这已经足够了,但比必要的还要多。 addStr
所需的最小不变量是 "the map must not contain a key that is the size of the map",因为这是我们打算插入的键。通过简化约束,我们也让它更容易满足:我们可以生成一个任意映射(其中可能包含坏键),然后删除坏键,给出一个令人满意的映射。
我还会注意到 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
的实例来做这样的事情,或者满足更强的不变量(其他测试可能需要) .我会把它留给你作为练习,但如果你在尝试时遇到困难,请随时提出更多问题。
我正在尝试测试自定义数据类型的逻辑。它接收一个 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 生成随机键,我不确定如何强制它为地图生成顺序键。缺少序列的问题是函数 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 生成完全任意的数据,然后从中构建满足不变量的数据(通过正在测试的系统外部的一些您认为正确的方法。
这种情况下的不变量给出为 "keys must be contiguous",但实际上是 "keys must be contiguous and start from 0"。这已经足够了,但比必要的还要多。 addStr
所需的最小不变量是 "the map must not contain a key that is the size of the map",因为这是我们打算插入的键。通过简化约束,我们也让它更容易满足:我们可以生成一个任意映射(其中可能包含坏键),然后删除坏键,给出一个令人满意的映射。
我还会注意到 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
的实例来做这样的事情,或者满足更强的不变量(其他测试可能需要) .我会把它留给你作为练习,但如果你在尝试时遇到困难,请随时提出更多问题。