如何正确约束“任意”UUID 生成?
How to properly constrain `arbitrary` UUID-Generation?
我正在尝试为我的某些类型创建 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
生成器来自上面。我错过了什么?
我通常发现在这种情况下,使用 newtype
定义 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) <- arbitrary
将 rid
解构为 UUID
值。
您可能会注意到我还为 Reservation
数据类型创建了 ValidReservation
newtype
。我 consistently do this to avoid orphan instances,并避免使用 QuickCheck 依赖项污染我的领域模型。 (我不反对 QuickCheck,但特定于测试的功能不属于 'production' 代码。)
这里显示的所有代码都是available in context on GitHub。
我正在尝试为我的某些类型创建 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
生成器来自上面。我错过了什么?
我通常发现在这种情况下,使用 newtype
定义 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) <- arbitrary
将 rid
解构为 UUID
值。
您可能会注意到我还为 Reservation
数据类型创建了 ValidReservation
newtype
。我 consistently do this to avoid orphan instances,并避免使用 QuickCheck 依赖项污染我的领域模型。 (我不反对 QuickCheck,但特定于测试的功能不属于 'production' 代码。)
这里显示的所有代码都是available in context on GitHub。