В 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})]
это просто то, что complier переводит выше. В ту минуту, когда вы идете дальше, вы столкнетесь с теми же ограничениями на то, что 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 для меня является использование straight MemberData
(что похоже на xUnit v1 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)
хотя это требует ручного кодирования, вы можете повторно использовать класс данных , что, по-видимому, полезно в реальных проектах, где мы часто проводим разные тесты с одними и теми же данными.
можно использовать 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.