当被测类型覆盖 ToString 时无法 运行 TestCaseSource 测试

Unable to run TestCaseSource tests when the type under test overrides ToString

首先,让我介绍 F# 中的测试设置(在 NUnit 之上使用 FsUnit):

type SimpleRecord = { A: int; B: int } 
                    override x.ToString() = x.A.ToString()

[<TestFixture>]
type ``Simple Test Cases``() =     
    static member SimpleDataSource =
        [|
            [|{ A = 1; B = 2} ,3|]
        |]

    [<TestCaseSource("SimpleDataSource")>]
    member x.``SimpleTest`` (testData: SimpleRecord * int) =
        let data, expected = testData
        data.A + data.B
        |> should equal expected

此测试将 运行 并按预期通过。但是,更改 ToString 覆盖以包含对 Guid.ToString() 的调用将阻止测试 运行:

type SimpleRecord = { A: int; B: int } 
                    override x.ToString() = x.A.ToString() + Guid.NewGuid().ToString()

进行上述更改后,测试仍会出现在测试资源管理器中,但不会 运行。即使右键单击它并选择 运行 Selected Tests 也不会执行测试。没有报告构建错误。

我也尝试过使用 DateTime.ToString() 而不是 Guid.ToString(),但这也拒绝 运行:

type SimpleRecord = { A: int; B: int } 
                    override x.ToString() = x.A.ToString() + DateTime.Now.ToString()

为什么在 ToString 覆盖中调用 Guid.ToString()DateTime.ToString() 覆盖正在测试的类型导致测试不是 运行ning?

Charlie Poole 提供了根本原因的解释:

When tests are run using the NUnit console or gui runners, they are first loaded (discovered) and then executed. When running under the adapter, the tests are loaded twice, once in the discovery phase and again at the beginning of the execution phase. This is a result of the way the VS Test Window works: discovery and execution are run in separate processes created by VS.

This doesn't cause a problem in most cases. The tests are loaded twice, which means the code for creating any test data is run twice. Usually, the same data is generated both times.

However, when using random data, it's known that different data will be generated in each phase. Tests that are shown in the initial discovery seem to disappear while "unknown" tests are run.

In your case, the guid is generated twice. The first one is used as part of the name of the test, consequently, that test disappears in the execution phase.

下面是一个演示解决方法的示例:

type ProblemRecord = { A: int; B: int } 
                     override x.ToString() = Guid.NewGuid().ToString()

[<TestFixture>]
type ``Test Cases``() =     
    // Using TestCaseData and explicitly setting a name will resolve the problem
    member x.SolutionDataSource =
        [
            TestCaseData(({ A = 1; B = 2} : ProblemRecord), 3)
                .SetName("Workaround")
        ]

    // This test case will be run by Test Explorer
    [<TestCaseSource("SolutionDataSource")>]
    member x.``SolutionTest`` (data: ProblemRecord, expected: int) =
        data.A + data.B
        |> should equal expected