【问题标题】:How to properly constrain `arbitrary` UUID-Generation?如何正确约束“任意”UUID-Generation?
【发布时间】:2020-08-28 00:34:58
【问题描述】:

我正在尝试为我的某些类型创建Arbitrary 实例,以用于QuickCheck 属性测试。我需要随机生成的 UUID,其约束条件是不允许使用全零 (nil) UUID - 即00000000-0000-0000-0000-000000000000。因此,我设置了以下生成器:

nonzeroIdGen :: Gen UUID.UUID
nonzeroIdGen = arbitrary `suchThat` (not . UUID.null)

我在Arbitrary 实例中使用如下:

instance Arbitrary E.EventId where
    arbitrary = do
        maybeEid <- E.mkEventId <$> nonzeroIdGen
        return $ fromJust maybeEid

一般来说,这是不安全的代码;但是对于测试,据说可以保证非零 UUID,我认为 fromJust 没问题。

mkEventId 定义为

mkEventId :: UUID.UUID -> Maybe EventId
mkEventId uid = EventId <$> validateId uid

使用 EventId 围绕 UUID.UUID 的新类型包装器,并且

validateId :: UUID.UUID -> Maybe UUID.UUID
validateId uuid = if UUID.null uuid then Nothing else Just uuid

令我惊讶的是,由于上述代码生成的全零 UUID,我的测试失败了。 mkEventId 中的 trace 显示以下内容:

00000001-0000-0001-0000-000000000001
Just (EventId {getEventId = 00000001-0000-0001-0000-000000000001})

00000000-0000-0000-0000-000000000000
Nothing
    Create valid Events. FAILED [1]

第一个生成的 ID 很好,第二个是全零,尽管我的 nonzeroIdGen 生成器来自上面。我错过了什么?

【问题讨论】:

  • mkEventId 的类型是什么?我不明白那个表达式是如何检查类型的......
  • @MarkSeemann,感谢您的帮助!我添加了类型签名。
  • 试图重现您的代码,mkEventId 不进行类型检查,但如果我将其更改为 mkEventId u = EventId &lt;$&gt; validateId u,它会这样做。
  • 啊,新手在这里发帖时试图让事情变得毫无意义的错误。
  • 从目前对问题的描述来看,我无法判断出了什么问题……看起来还可以。然后,您不会显示失败的测试的定义。如果您发布 minimal working example... 可能会有所帮助

标签: haskell uuid quickcheck


【解决方案1】:

我通常发现在这种情况下,使用newtypes 来定义Arbitrary 的实例会更好。这是我为有效的UUID 值制作的:

newtype NonNilUUID = NonNilUUID { getNonNilUUID :: UUID } deriving (Eq, Show)

instance Arbitrary NonNilUUID where
  arbitrary = NonNilUUID <$> arbitrary `suchThat` (/= nil)

然后您可以从该实例中组合其他 Arbitrary 实例,就像我在这里使用 Reservation 数据类型所做的那样:

newtype ValidReservation =
  ValidReservation { getValidReservation :: Reservation } deriving (Eq, Show)

instance Arbitrary ValidReservation where
  arbitrary = do
    (NonNilUUID rid) <- arbitrary
    (FutureTime d) <- arbitrary
    n <- arbitrary
    e <- arbitrary
    (QuantityWithinCapacity q) <- arbitrary
    return $ ValidReservation $ Reservation rid d n e q

注意模式匹配 (NonNilUUID rid) &lt;- arbitraryrid 解构为 UUID 值。

您可能会注意到我还为我的Reservation 数据类型创建了一个ValidReservation newtype。我consistently do this to avoid orphan instances,并避免使用 QuickCheck 依赖项污染我的域模型。 (我不反对 QuickCheck,但特定于测试的功能不属于“生产”代码。)

这里显示的所有代码都是available in context on GitHub

【讨论】:

  • 感谢提示,我会尝试用这种方式重构我的代码。
  • “作曲更好”到底是什么意思?
  • @UlrichSchuster 这很容易重用,例如NonNilUUIDArbitrary 实例定义更复杂的Arbitrary 实例,如上所示。还要注意示例中的其他特定于测试的 newtype 包装器:FutureTimeQuantityWithinCapacity
猜你喜欢
  • 2022-11-02
  • 2019-02-19
  • 1970-01-01
  • 1970-01-01
  • 2022-07-11
  • 1970-01-01
  • 1970-01-01
  • 2017-06-14
  • 2023-03-16
相关资源
最近更新 更多