F# - 如何正确实现安装程序并使用NUnit初始化变量

Dav*_*nez 7 f# unit-testing

我正在第一次潜入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)
Run Code Online (Sandbox Code Playgroud)

那些[<DefaultValue>] val mutable声明变量(没有初始化,因为那是SetUp作业)并使这些变量可用于所有类范围,并且事实上我基本上完成了我在C#中的1:1翻译而没有任何明显的语法增益可读性给了我冷颤.有没有办法重写这些看起来更好的测试和设置?因为我在互联网上看到的所有例子都很简单,很小,并没有涵盖这些案例.

Mar*_*ann 6

让我们首先将问题减少到更易于管理的大小:

减少了问题

在此测试中,您在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)
Run Code Online (Sandbox Code Playgroud)

显然,这是真正问题的替身.

返回值的函数

为什么不编写初始化所需值的函数,而不是设置类字段?

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)
Run Code Online (Sandbox Code Playgroud)

如果您不想同时使用所有值(就像您可以访问类中的所有字段一样),您可以定义一个函数来返回元组或记录中的所有值:

    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)
Run Code Online (Sandbox Code Playgroud)

此示例使用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)
Run Code Online (Sandbox Code Playgroud)

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

如果你想了解更多关于使用F#的惯用单元测试,一个好的起点可能是我的Pluralsight课程,关于用F#进行单元测试.

  • @DavidJiménezMartínez是的,您必须复制该单个方法调用,但请考虑一下:您是否可以隐式访问所有类字段,这会隐藏测试的设计(和可维护性)问题?do*all*测试是否使用*all*类字段?在我的职业生涯中,我已经看到了无数测试类的例子,其中有太多的字段,其中一半的测试没有使用.你应该接受'生产'代码中的这种类设计吗?我不认为你应该这样做,所以你也不应该在测试代码中接受它:你也必须维护测试代码. (2认同)