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

时间:2016-01-27 00:36:20

标签: .net f# tdd 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);
        }
    }
}

在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)

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

  

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

     

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

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

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

6 个答案:

答案 0 :(得分:9)

this question所述,您只能使用InlineData的文字。列表不是文字。

但是,xUnit提供了ClassData,它似乎可以满足您的需求。

This question讨论了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)

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

答案 1 :(得分:7)

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

[InlineData(1,2)]

您的阵列构造版本: -

[InlineData( new object[] {1,2})]

就是编译器将上述内容翻译成的内容。一旦你走得更远,你将对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)

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

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

然后(如果我们愿意放弃懒惰),我们可以滥用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)

但是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)

然而,对我来说,xUnit v2最常用的是使用直接MemberData(类似于xUnit v1的PropertyData,但是也可以在字段上使用): -

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

要做到的关键是将: seq<obj>(或: obj array seq)放在序列的声明上,否则xUnit会向你抛出。

答案 2 :(得分:3)

在@Assassin的绝妙答案上进一步发展-现在我们具有隐式收益,您可以将测试用例放入数组中,并省去yield s。我也很想添加一个厚脸皮的小私有运算符来处理对象转换。因此:

open System
open Xunit

let inline private (~~) x = x :> Object

let degreesToRadiansCases =
    [|
        // Degrees; Radians
        [|   ~~0.0;            ~~0.0  |]
        [| ~~360.0; ~~(Math.PI * 2.0) |]
    |]

[<Theory>]
[<MemberData("degreesToRadiansCases")>]
let ``Convert from degrees to radians`` (degrees, radians) =
    let expected = radians
    let actual = Geodesy.Angle.toRadians degrees
    Assert.Equal(expected, actual)

let stringCases =
    [|
        [| ~~99; ~~"hello1" |] 
        [| ~~99; ~~"hello2" |] 
    |]

[<Theory>]
[<MemberData("stringCases")>]
let ``tests`` (i, s) =
    printfn "%i %s" i s
    Assert.Equal(s, "hello1")

答案 3 :(得分:2)

您可以在此处使用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)

这是一种方式:

|> Seq.map FSharpValue.GetTupleFields

关键是IEnumerable<obj[]>行。 它采用元组列表(您必须使用元组来允许不同的参数类型)并将其转换为XUnit期望的$('.activeNav li a').click(function(e) { e.preventDefault(); $(this).closest('.activeNav').find('a').removeClass('active');//from the current element find the closest parent with the class activeNav and then find all links to remove the active class $(this).addClass('active'); });

答案 4 :(得分:2)

您也可以使用不带类的成员数据:

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

[<Theory>]
[<MemberData("memberDataProperty")>]
let ``Can use MemberData`` param1 param2 expectedResult = ...

答案 5 :(得分:0)

一种可能性是使用xUnit的MemberData属性。这种方法的一个缺点是,此参数化测试在Visual Studio的“测试资源管理器”中显示为一个测试,而不是两个单独的测试,因为集合缺少xUnit的IXunitSerializable接口,并且xUnit也未为该类型添加内置的序列化支持。有关更多信息,请参见xunit/xunit/issues/429

这是一个最小的工作示例。

module TestModule

  open Xunit

  type TestType () =
    static member TestProperty
      with get() : obj[] list =
        [
          [| [0]; "a" |]
          [| [1;2]; "b" |]
        ]

    [<Theory>]
    [<MemberData("TestProperty")>]            
    member __.TestMethod (a:int list) (b:string) =
      Assert.Equal(1, a.Length)

另请参阅此similar question,其中我给出了similar answer