4

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

当前的问题是:我设法生成随机图(用于测试某些图功能),但是当测试失败时,FsCheck 会吐出整个图并且它不使用 ToString 因此它实际上会转储原始记录列表,并且您看不到任何内容那里。

此外,我不仅需要用于检查的输入图,还需要我在运行属性时创建的一些其他数据。

那么如何更改 FsCheck 的输出行为以便

  • 实际上在输入图上调用我的 ToString 方法
  • 输出更多信息

当测试失败?

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

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
4

2 回答 2

2

FsCheck 用于sprintf "%A"将测试参数转换为测试输出中的字符串,因此您需要做的是控制格式化程序如何格式化您的类型%A。根据如何使用 printf 自定义自定义类型的输出?,做到这一点的方法是使用StructuredFormatDisplay属性。该属性的值应该是格式中的字符串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()部分。(我不记得了,也无法通过快速谷歌找到,覆盖应该在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在您的输出中显示为文本,并且您将能够更好地专注于树结构——并且无需在类型上设置StructuredFormatDisplay属性。Tree

这不是一个完全理想的解决方案,因为您可能需要根据StructuredFormatDisplay您正在运行的各种测试的需要不时调整属性。(对于某些测试,您可能希望专注于叶子数据的一部分,对于其他测试,您可能希望完全忽略叶子数据,等等)。而且您可能希望在投入生产之前删除该属性。但在 FsCheck 获得“给我一个函数来格式化失败的测试数据”配置参数之前,这是让你的测试数据按照你需要的方式格式化的最佳方式。

于 2017-08-02T07:03:03.977 回答
0

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

于 2017-08-18T22:05:34.667 回答