在F#中,如何将集合传递给xUnit的InlineData属性

Mik*_*ris 8 .net tdd f# xunit

我想要将列表,数组和/或seq用作xUnit的InlineData的参数.

在C#中,我可以这样做:

using Xunit; //2.1.0

namespace CsTests
{
    public class Tests
    {
        [Theory]
        [InlineData(new[] {1, 2})]
        public void GivenCollectionItMustPassItToTest(int[] coll)
        {
            Assert.Equal(coll, coll);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在F#我有这个:

namespace XunitTests

module Tests =
  open Xunit //2.1.0

  [<Theory>]
  [<InlineData(8)>]
  [<InlineData(42)>]
  let ``given a value it must give it to the test`` (value : int) =
    Assert.Equal(value, value)

  [<Theory>]
  [<InlineData([1; 2])>]
  let ``given a list it should be able to pass it to the test``
  (coll : int list) =
    Assert.Equal<int list>(coll, coll)

  [<Theory>]
  [<InlineData([|3; 4|])>]
  let ``given an array it should be able to pass it to the test``
  (coll : int array) =
    Assert.Equal<int array>(coll, coll)
Run Code Online (Sandbox Code Playgroud)

F#代码提供以下构建错误:

Library1.fs(13,16):这不是有效的常量表达式或自定义属性值

Library1.fs(18,16):这不是有效的常量表达式或自定义属性值

参考第二和第三测试理论.

是否可以使用xUnit将集合传递给InlineData属性?

byt*_*ter 9

本问题所述,您只能使用文字InlineData.列表不是文字.

但是,xUnit提供了ClassData哪些似乎可以满足您的需求.

这个问题讨论了C#的同样问题.

为了ClassData与测试一起使用,只需要创建一个数据类seq<obj[]>:

type MyArrays () =    
    let values : seq<obj[]>  =
        seq {
            yield [|3; 4|]    // 1st test case
            yield [|32; 42|]  // 2nd test case, etc.
        }
    interface seq<obj[]> with
        member this.GetEnumerator () = values.GetEnumerator()
        member this.GetEnumerator () =
            values.GetEnumerator() :> System.Collections.IEnumerator

module Theories = 
    [<Theory>]
    [<ClassData(typeof<MyArrays1>)>]
    let ``given an array it should be able to pass it to the test`` (a : int, b : int) : unit = 
        Assert.NotEqual(a, b)
Run Code Online (Sandbox Code Playgroud)

虽然这需要一些手动编码,但您可以重复使用数据类,这似乎在实际项目中很有用,我们经常针对相同的数据运行不同的测试.


Rub*_*ink 7

InlineDataAttribute倾向于C#params机制.这是在C#中启用InlineData的默认语法的原因: -

[InlineData(1,2)]
Run Code Online (Sandbox Code Playgroud)

您的版本与数组结构: -

[InlineData( new object[] {1,2})]
Run Code Online (Sandbox Code Playgroud)

简单来说就是编译器将上述内容翻译成的内容.一旦你走得更远,你将对CLI实际启用的内容产生相同的限制 - 底线是在IL级别,使用属性构造函数意味着在编译时需要将所有内容归结为常量.上述语法的F#等价物只是:[<InlineData(1,2)>],所以你问题的直接答案是:

module UsingInlineData =
    [<Theory>]
    [<InlineData(1, 2)>]  
    [<InlineData(1, 1)>]  
    let v4 (a : int, b : int) : unit = Assert.NotEqual(a, b)
Run Code Online (Sandbox Code Playgroud)

我无法避免在@ bytebuster的例子上匆匆而过:)如果我们定义一个帮助器: -

type ClassDataBase(generator : obj [] seq) = 
    interface seq<obj []> with
        member this.GetEnumerator() = generator.GetEnumerator()
        member this.GetEnumerator() = 
            generator.GetEnumerator() :> System.Collections.IEnumerator
Run Code Online (Sandbox Code Playgroud)

然后(如果我们愿意放弃懒惰),我们可以滥用list以避免使用seq/ yield赢得代码高尔夫: -

type MyArrays1() = 
    inherit ClassDataBase([ [| 3; 4 |]; [| 32; 42 |] ])

[<Theory>]
[<ClassData(typeof<MyArrays1>)>]
let v1 (a : int, b : int) : unit = Assert.NotEqual(a, b)
Run Code Online (Sandbox Code Playgroud)

但原始语法seq可以做得足够干净,所以不需要像上面那样使用它,而是我们这样做:

let values : obj array seq = 
    seq { 
        yield [| 3; 4 |] 
        yield [| 32; 42 |] 
    }

type ValuesAsClassData() = 
    inherit ClassDataBase(values)

[<Theory; ClassData(typeof<ValuesAsClassData>)>]
let v2 (a : int, b : int) : unit = Assert.NotEqual(a, b)
Run Code Online (Sandbox Code Playgroud)

但是,xUnit v2对我来说最常用的是直接使用MemberData (就像xUnit v1一样,PropertyData但是也可以在字段上使用): -

[<Theory; MemberData("values")>]
let v3 (a : int, b : int) : unit = Assert.NotEqual(a, b)
Run Code Online (Sandbox Code Playgroud)

正确的关键是将: seq<obj>(或: obj array seq)放在序列的声明上,否则xUnit将向你抛出.

  • 它不是"理论"或"会员数据"吗?如果它是后者,那么你应该能够使`PropertyData`工作 - 只需将`get()`添加到字段中?你有充足的替代品是对的 - 一般而言,理论的每一段都是一个可单独寻址的事实并且可能最终得到更易读的代码. (2认同)

Ass*_*sin 6

您还可以使用没有类的成员数据:

let memberDataProperty = seq {
    yield [|"param1":> Object; param2 :> Object; expectedResult :> Object |]
}

[<Theory; MemberData("memberDataProperty")>]
let ``Can use MemberData`` param1 param2 expectedResult = ...
Run Code Online (Sandbox Code Playgroud)


cor*_*rax 5

您可以在FSharp.Reflection此处使用命名空间以获得良好的效果。考虑一些isAnswer : (string -> int -> bool)您想用几个例子测试的假设函数。

这是一种方法:

open FSharp.Reflection
open Xunit

type TestData() =
  static member MyTestData =
    [ ("smallest prime?", 2, true)
      ("how many roads must a man walk down?", 41, false) 
    ] |> Seq.map FSharpValue.GetTupleFields

[<Theory; MemberData("MyTestData", MemberType=typeof<TestData>)>]
let myTest (q, a, expected) =
  Assert.Equals(isAnswer q a, expected)
Run Code Online (Sandbox Code Playgroud)

关键是|> Seq.map FSharpValue.GetTupleFields线。它接受元组列表(您必须使用元组来允许不同的参数类型)并将其转换为IEnumerable<obj[]>XUnit 期望的类型。