我正在第一次潜入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翻译而没有任何明显的语法增益可读性给了我冷颤.有没有办法重写这些看起来更好的测试和设置?因为我在互联网上看到的所有例子都很简单,很小,并没有涵盖这些案例.
让我们首先将问题减少到更易于管理的大小:
在此测试中,您在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#进行单元测试.