具有生成字段的记录类型的相等性测试

Equality test for a record type with generated fields

如果记录类型包含生成的字段,例如自动生成的 ID 和时间戳,例如:

type UserCreated = {
    Id: Guid
    Name: string
    CreationTime: Instant
}

为此编写单元测试的最佳方法是什么?不可能简单地断言记录等于任何特定值,因为无法提前知道 Id 和 CreationTime 值是什么。

可能的解决方案:

最好的方法是什么?

您并没有真正测试记录类型本身——它只是无意义的数据。您要测试的是对该数据进行操作的函数。

当一个函数是纯函数时,它很容易测试。调用 Guid.NewGuid()DateTime.Now 都是副作用,我假设您对这里的两个有问题的字段所做的。因此,将这两位重构为您作为参数显式传递的函数将是我书中的方法(您的要点 2)。这将使代码更易于测试并且更纯粹(也许可读性也更差一些,我想你需要平衡一下)。

也就是说 - 对于大多数涉及您的记录的测试用例,您应该在安排测试时知道这些字段的值。只有当您测试创建记录 'from nothing' 的函数时,您事先并不知道它们,对于这种情况,您可以只检查 Name 字段是否具有预期值(所以你的要点1 必要时,比较其他地方的记录 'as is')。

编辑:不知道大家有没有漏掉,还有一种可能是在测试项目中定义一个专门的比较函数,保证'generated' 两个记录中的值在比较它们之前是相同的:

let compareWithoutGenerated (a: UserCreated) (b: UserCreated) = 
    a = { b with Id = a.Id; CreationTime = a.CreationTime }

然后:

WhateverUnit.Assert.True(compareWithoutGenerated a b)

显然这很丑陋,但我会说这是公平的测试游戏。可以说有更好的方法来做到这一点,但至少,这种方法不会对您的生产代码造成测试引起的损害。

我的方法确实是将一个函数传递给处理 ID 和日期生成的用户创建函数。如果您必须传入的参数太多,那么这可能是您需要重构设计的线索。

这里是领域层,例如:

// =================
// Domain
// =================
open System

type UserCreated = {
    Id: Guid
    Name: string
    CreationTime: DateTime
}

// the generation functions are passed in
let createCompleteRecord generateGuid generateTime name = 
    {
        Id = generateGuid()
        Name = name  // add validation?
        CreationTime = generateTime()
    }

在应用程序代码中,您将使用部分应用程序来烘焙生成器并创建一个有用的函数 createRecord

// =================
// Application code
// =================
let autoGenerateGuid() = Guid.NewGuid()
let autoGenerateTime() = DateTime.UtcNow

// use partial application to get a useful version
let createRecord = createCompleteRecord autoGenerateGuid autoGenerateTime

let recForApp = createRecord "myname"

在测试代码中,您将使用部分应用程序烘焙其他生成器并创建不同的函数 createRecordForTesting

// =================
// Test code
// =================

let explicitGenerateGuid() = Guid.Empty
let explicitGenerateTime() = DateTime.MinValue

// use partial application to get a useful version
let createRecordForTesting = createCompleteRecord explicitGenerateGuid explicitGenerateTime

let recForTest = createRecordForTesting "myname"

Assert.AreEqual(Guid.Empty,recForTest.Id)
Assert.AreEqual("myname",recForTest.Name)
Assert.AreEqual(DateTime.MinValue,recForTest.CreationTime)

并且由于 "generated" 字段现在具有硬编码值,您还可以测试整个记录相等逻辑:

let recForTest1 = createRecordForTesting "myname"
let recForTest2 = createRecordForTesting "myname"
Assert.AreEqual(recForTest1,recForTest2)