自定义 FsCheck 输出

Customise FsCheck output

我正在 VisualStudio 中使用 FsCheck 和 NUnit 进行测试。

目前的问题是:我设法生成了随机图(用于测试某些图功能)但是当测试失败时,FsCheck 吐出整个图并且它不使用 ToString 所以它直接转储原始记录列表和你什么也看不到。

此外,我不仅需要用于检查的输入图,还需要我在 运行 a 属性.

时创建的一些其他数据

那么我该如何更改 FsCheck 的输出行为才能

什么时候测试失败了?

编辑: 这是我当前的测试设置。

module GraphProperties

open NUnit.Framework
open FsCheck
open FsCheck.NUnit

let generateRandomGraph =
    gen {
        let graph: Graph<int,int> = Graph<_,_>.Empty()
        // fill in random nodes and transitions...
        return graph
    }

type MyGenerators =
    static member Graph() =
        {new Arbitrary<Graph<int,int>>() with
            override this.Generator = generateRandomGraph
            override this.Shrinker _ = Seq.empty }

[<TestFixture>]
type NUnitTest() =
    [<Property(Arbitrary=[|typeof<MyGenerators>|], QuietOnSuccess = true)>]
    member __.cloningDoesNotChangeTheGraph (originalGraph: Graph<int,int>) =
        let newGraph = clone originalGraph
        newGraph = originalGraph

FsCheck 使用 sprintf "%A" 将测试参数转换为测试输出中的字符串,因此您需要做的是控制 %A 格式化程序如何格式化您的类型。根据How do I customize output of a custom type using printf?, the way to do that is with the StructuredFormatDisplay attribute。该属性的值应该是 PreText {PropertyName} PostText 格式的字符串,其中 PropertyName 应该是 属性( 不是 函数!)类型。例如,假设您有一个树结构,叶子中包含一些复杂的信息,但对于您的测试,您只需要知道叶子的数量,而不是叶子中的内容。因此,您将从这样的数据类型开始:

// Example 1
type ComplicatedRecord = { ... }
type Tree =
    | Leaf of ComplicatedRecord
    | Node of Tree list
    with
        member x.LeafCount =
            match x with
            | Leaf _ -> 1
            | Node leaves -> leaves |> List.sumBy (fun x -> x.LeafCount)
        override x.ToString() =
            // For test output, we don't care about leaf data, just count
            match x with
            | Leaf -> "Tree with a total of 1 leaf"
            | Node -> sprintf "Tree with a total of %d leaves" x.LeafCount

现在,到目前为止,这不是您想要的。此类型 没有 声明了自定义 %A 格式,因此 FsCheck(以及使用 sprintf "%A" 对其进行格式化的任何其他内容)最终将输出整个复杂的结构树及其所有与测试无关的叶数据。为了让 FsCheck 输出你想看到的,你需要设置一个 属性,而不是一个函数(ToString 不能用于这个目的) 将输出您想要看到的内容。例如:

// Example 2
type ComplicatedRecord = { ... }
[<StructuredFormatDisplay("{LeafCountAsString}")>]
type Tree =
    | Leaf of ComplicatedRecord
    | Node of Tree list
    with
        member x.LeafCount =
            match x with
            | Leaf _ -> 1
            | Node leaves -> leaves |> List.sumBy (fun x -> x.LeafCount)
        member x.LeafCountAsString = x.ToString()
        override x.ToString() =
            // For test output, we don't care about leaf data, just count
            match x with
            | Leaf -> "Tree with a total of 1 leaf"
            | Node -> sprintf "Tree with a total of %d leaves" x.LeafCount

注意:我没有在 F# 中对此进行测试,只是将其输入到 Stack Overflow 评论框中——所以我可能搞砸了 ToString() 部分。 (我不记得,也无法快速找到 Google,覆盖应该在 with 关键字之后还是之前)。但我知道 StructuredFormatDisplay 属性是你想要的,因为我自己使用它从 FsCheck 中获取自定义输出。

顺便说一句,您也可以在我的示例中为复杂的记录类型设置一个 StructuredFormatDisplay 属性。例如,如果你有一个你关心树结构而不关心叶子内容的测试,你可以这样写:

// Example 3
[<StructuredFormatDisplay("LeafRecord")>] // Note no {} and no property
type ComplicatedRecord = { ... }
type Tree =
    | Leaf of ComplicatedRecord
    | Node of Tree list
    with
        member x.LeafCount =
            match x with
            | Leaf _ -> 1
            | Node leaves -> leaves |> List.sumBy (fun x -> x.LeafCount)
        override x.ToString() =
            // For test output, we don't care about leaf data, just count
            match x with
            | Leaf -> "Tree with a total of 1 leaf"
            | Node -> sprintf "Tree with a total of %d leaves" x.LeafCount

现在,您所有的 ComplicatedRecord 实例,无论其内容如何,​​都将在您的输出中显示为文本 LeafRecord,您将能够更好地关注树结构 - - 并且不需要在 Tree 类型上设置 StructuredFormatDisplay 属性。

这不是一个完全理想的解决方案,因为您可能需要不时调整 StructuredFormatDisplay 属性,以满足您 运行 的各种测试的需要。 (对于某些测试,您可能希望关注叶数据的一部分,对于其他测试,您可能希望完全忽略叶数据,等等)。在投入生产之前,您可能希望删除该属性。但在 FsCheck 获取 "Give me a function to format failed test data with" 配置参数之前,这是按照您需要的方式格式化测试数据的最佳方式。

您还可以使用标签在测试失败时显示您想要的任何内容:https://fscheck.github.io/FsCheck/Properties.html#And-Or-and-Labels