unit-testing - F# - 如何使用 NUnit 正确实现设置和初始化变量

标签 unit-testing f#

我在工作中第一次深入研究 F#,我正在将几个 C# 单元测试转移到 F# 作为练习。我们的测试相当复杂,但我喜欢挑战(包括设置、继承、拆卸等)。

正如我所看到的,如果可能的话,应该避免可变性,但是在编写 [SetUp] 时部分测试我似乎无法找到跳过可变性的方法。为测试创建虚拟 XML 的示例:

[<TestFixture>]
type CaseRuleFixture() = 

    [<DefaultValue>] val mutable xsl : XNamespace
    [<DefaultValue>] val mutable simpleStylesheet : XElement
    [<DefaultValue>] val mutable testNode : XElement
    [<DefaultValue>] val mutable rootNode : XElement
    [<DefaultValue>] val mutable root : XElement

    let CreateXsltHeader(xsl: XNamespace) =
        // Build XSLT header
        let styleSheetRoot = 
            new XElement(
                xsl + "stylesheet", 
                new XAttribute(XName.Get "version", "1.0"), 
                new XAttribute(XNamespace.Xmlns + "xsl", "http://www.w3.org/1999/XSL/Transform"), 
                new XAttribute(XNamespace.Xmlns + "msxsl", "urn:schemas-microsoft-com:xslt"),
                new XAttribute(XName.Get "exclude-result-prefixes", "msxsl"),
                new XAttribute(XNamespace.Xmlns + "utils", "urn:myExtension"))

        let outputNode = 
            new XElement(
                xsl + "output", 
                new XAttribute(XName.Get "method", "xml"), 
                new XAttribute(XName.Get "indent", "yes"))

        styleSheetRoot.Add outputNode
        styleSheetRoot


    [<SetUp>]
    member this.SetUp() =
        this.xsl <- XNamespace.Get "http://www.w3.org/1999/XSL/Transform"
        this.simpleStylesheet <- CreateXsltHeader(this.xsl)

        Directory.EnumerateFiles "Templates"
        |> Seq.iter(fun filepath -> this.simpleStylesheet.Add(XElement.Parse(File.ReadAllText filepath).Elements()))

        let variable = 
            new XElement(
                this.xsl + "variable", 
                new XAttribute(XName.Get "name", "ROOT"), 
                new XAttribute(XName.Get "select", "ROOT"))

        this.simpleStylesheet.Add(variable)

        let rootTemplate = new XElement(this.xsl + "template", new XAttribute(XName.Get "match", "/ROOT"))
        this.simpleStylesheet.Add(rootTemplate);

        this.rootNode <- new XElement(XName.Get "ROOT")
        rootTemplate.Add(this.rootNode);

        this.root <- new XElement(XName.Get "ROOT")
        this.testNode <- new XElement(XName.Get "TESTVALUE")

        this.root.Add(this.testNode)

    [<Test>]
    member this.CaseCapitalizeEachWordTest() =
        this.testNode.Value <- " text to replace ";

        let replaceRule = new CaseRule();
        replaceRule.Arguments <- [| "INITIALS" |];
        this.rootNode.Add(
            replaceRule.ApplyRule [| new XElement(this.xsl + "value-of", new XAttribute(XName.Get "select", "TESTVALUE")) |]);

        let parser = new XsltParserHelper(this.simpleStylesheet);
        let result = parser.ParseXslt(this.root);

        let value = result.DescendantsAndSelf() |> Seq.find(fun x -> x.Name = XName.Get "ROOT")

        Assert.AreEqual(" Text To Replace ", value.Value)

那些[<DefaultValue>] val mutable声明变量(不初始化,因为这是 SetUp 工作)并使这些变量可用于所有类范围,事实上我基本上已经从 C# 中进行了 1:1 的转换,而没有任何明显的语法和可读性让我不寒而栗。有没有办法重写这些看起来更好的测试和设置?因为我在互联网上看到的所有例子都很简单,很小,不包括这些情况。

最佳答案

让我们首先将问题缩小到更易于管理的规模:

减少问题

在此测试中,您在 SetUp 中初始化了两个可变字段。方法:

[<TestFixture>]
type MutableTests() = 
    [<DefaultValue>] val mutable foo : int
    [<DefaultValue>] val mutable bar : int

    [<SetUp>]
    member this.SetUp () =
        this.foo <- 42
        this.bar <- 1337

    [<Test>]
    member this.TheTest () = 
        Assert.AreEqual(42, this.foo)
        Assert.AreEqual(1337, this.bar)

显然,这是对真正问题的替代。

返回值的函数

与其设置类字段,不如编写函数来初始化您需要的值?
module BetterTests =
    let createDefaultFoo () = 42
    let createDefaultBar () = 1337

    [<Test>]
    let ``a test using individual creation functions`` () =
        let foo = createDefaultFoo ()
        let bar = createDefaultBar ()

        Assert.AreEqual(42, foo)
        Assert.AreEqual(1337, bar)

如果您不想一次获得所有值(就像您可以访问类中的所有字段一样),您可以定义一个函数来返回元组或记录中的所有值:
    let createAllDefaultValues () = createDefaultFoo (), createDefaultBar ()

    [<Test>]
    let ``a test using a single creation function`` () =
        let foo, bar = createAllDefaultValues ()

        Assert.AreEqual(42, foo)
        Assert.AreEqual(1337, bar)

此示例使用 int * int元组,但定义记录可能更具可读性:
    type TestValues = { Foo : int; Bar : int }

    let createDefaultTestValues () = {
        Foo = createDefaultFoo ()
        Bar = createDefaultBar () }

    [<Test>]
    let ``a test using a single creation function that returns a record`` () =
        let defaultValues = createDefaultTestValues ()

        Assert.AreEqual(42, defaultValues.Foo)
        Assert.AreEqual(1337, defaultValues.Bar)

请注意,与 C# 中的类不同,F# 中的记录声明是超轻量级的。

如果您想了解有关使用 F# 进行惯用单元测试的更多信息,可以从 my Pluralsight course about unit testing with F# 开始。 .

关于unit-testing - F# - 如何使用 NUnit 正确实现设置和初始化变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32459246/

相关文章:

f# - fscheck 生成大小在 min 和 max 之间的字符串

excel - F# ExcelUsedRange 没有属性或方法

list - 两个列表 F# 之间的交集

java - 单元测试是否已捕获并处理异常

c++ - 如何使用 BUCK 仅运行特定的 C++ gtest

java - SpringBoot @WebMvcTest 和 @MockBean 没有按预期工作

unit-testing - 在 Angular2 测试中模拟 ngrx/store

C# Deedle AggregateRowsBy/数据透视表示例

.net - 在 F# 中使用 MSTest

c# - 在单元测试中方法执行后设置变量名称