我想将列表,数组和/或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属性?
答案 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。