В F# как передать коллекцию в атрибут InlineData xUnit
Я хотел бы использовать список, массив и / или seq в качестве параметра InlineData для xUnit.
В 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): это недопустимое константное выражение или значение пользовательского атрибута
Ссылаясь на 2-й и 3-й тестовые теории.
Можно ли использовать xUnit для передачи коллекций в атрибут InlineData?
3 ответа
InlineDataAttribute
опирается на C# params
механизм. Это то, что включает синтаксис по умолчанию InlineData в C#:
[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's PropertyData
но обобщенный, чтобы работать на полях):-
[<Theory; MemberData("values")>]
let v3 (a : int, b : int) : unit = Assert.NotEqual(a, b)
Главное, чтобы получить право, это поставить : seq<obj>
(или же : obj array seq
) на объявлении последовательности или xUnit скинет на вас.
Как описано в этом вопросе, вы можете использовать только литералы с 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)
Хотя это требует некоторого ручного кодирования, вы можете повторно использовать класс данных, который, по-видимому, полезен в реальных проектах, где мы часто проводим разные тесты для одних и тех же данных.
Вы также можете использовать данные члена без класса:
let memberDataProperty:=
seq {
yield [|"param1":> Object; param2 :> Object; expectedResult :> Object |]
}
[<Theory>]
[<MemberData("memberDataProperty")>]
let ``Can use MemberData`` param1 param2 expectedResult = ...
Вы можете использовать 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)
Ключевым моментом является |> Seq.map FSharpValue.GetTupleFields
линия. Он берет список кортежей (вы должны использовать кортежи, чтобы разрешить разные типы аргументов) и преобразует его в IEnumerable<obj[]>
что XUnit ожидает.
Одна из возможностей заключается в использовании xUnit's MemberData
приписывать. Недостатком этого подхода является то, что этот параметризованный тест отображается в обозревателе тестов Visual Studio как один тест вместо двух отдельных тестов, поскольку в коллекциях отсутствует xUnit. IXunitSerializable
interface и 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)
Смотрите также этот похожий вопрос, в котором я даю аналогичный ответ.
Опираясь на блестящий ответ @Assassin - теперь у нас есть неявные выходы, вы можете поместить тестовые примеры в массив и обойтись без yield
с. У меня также возникнет соблазн добавить нахальный частный оператор для обработки преобразований объектов. Таким образом:
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")