2016-01-27 54 views
6

Chciałbym być zamiar użyć listy, tablicy i/lub seq jako parametr do InlineData xUnit's.W F # jak przekazać kolekcję do atrybutu InlineData xUnit

W języku C# można to zrobić:

using Xunit; //2.1.0 

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

w F # mam to:

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 # Kod podać następujące zbudować błędy:

Library1.fs (13, 16): nie jest to poprawne wyrażenie stałe lub wartość atrybutu niestandardowego

Library1.fs (18, 16), to nie jest ważny stałej ekspresji lub niestandardowy wartość atrybutu

Odnosząc się do teorii, 2 i 3 testu.

Czy można użyć xUnit do przekazania w kolekcjach do atrybutu InlineData?

+0

Dzięki James, jeśli tak jest, to w jaki sposób ludzie zwykle potrzebują zestawu danych testowych w F #? –

+1

Zasugerowałem, że jest to duplikat http://stackoverflow.com/questions/29349152/cannot-create-list-literal-in-f. Jednak to pytanie jest w szczególności, jak stworzyć literalną listę w F # (a odpowiedź brzmi: nie możesz). To pytanie brzmi "Jak mogę użyć xUnit i przekazać na listę danych testowych". Wygląda na to, że @bytebuster ma praktyczną odpowiedź. –

+2

Nie można używać list, ale powinieneś być w stanie używać tablic jak w C#, ale nigdy nie udało mi się go skompilować. Na szczęście staje się to nieistotne, gdy odkryjesz [FsCheck.Xunit] (https://fscheck.github.io/FsCheck/RunningTests.html). –

Odpowiedz

3

pochyla się na mechanizm C# params. To, co umożliwia domyślną składnię InlineData w C#: -

[InlineData(1,2)] 

Twoja wersja z budową tablicy: -

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

jest po prostu to, co kompilator tłumaczy wyżej. Z chwilą, gdy pójdziesz dalej, uruchomisz te same restrykcje, co faktycznie umożliwi interfejs CLI. Najważniejsze jest to, że na poziomie IL, użycie konstruktorów atrybutów oznacza, że ​​wszystko musi zostać sprowadzone do stałych w czasie kompilacji.F # odpowiednik powyższej składni jest prosta: [<InlineData(1,2)>], więc bezpośrednią odpowiedź na Twoje pytanie brzmi:

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

nie udało mi się uniknąć riffy na użytkownika @ bytebuster przykład chociaż :) Jeśli zdefiniujemy pomocnika: -

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

Następnie (jeśli jesteśmy gotowi zrezygnować z lenistwa), możemy nadużywać list celu uniknięcia konieczności korzystania seq/yield zdobyć kod do golfa: -

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

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

Ale surowy składnia seq można dostatecznie czysty, więc nie ma rzeczywistej potrzeby, aby używać go jak wyżej, zamiast tego zrobić:

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) 

Jednak najbardziej idiomatyczne z xUnit v2 dla mnie jest użycie prosto MemberData (co jest jak xUnit v1 na PropertyData ale uogólnić również pracować na polach): -

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

klucz Rzeczą, którą należy zrobić, jest umieszczenie : seq<obj> (lub : obj array seq) na deklaracji sekwencji lub xUnit rzuci się na ciebie.

+0

Dzięki temu działa, ale NCrunch nie wydaje się podobać MemberData. O, cóż, najprawdopodobniej użyję Fakty lub FsCheck w przyszłości. –

+1

Czy to 'Teoria' lub' MemberData' to nie lubi? Jeśli to drugie, powinieneś być w stanie wykonać działanie 'PropertyData' - wystarczy dodać' with get() 'do pola? Masz rację, jeśli masz dostatecznie dużo zamienników - na ogół łatwo jest sprawić, by każda noga Teorii była indywidualnie adresowalnym faktem i prawdopodobnie skończy się bardziej czytelnym kodem. –

5

Jak opisano w this question, można używać literałów tylko z InlineData. Listy nie są literałami.

Jednak xUnit zapewnia ClassData, który wydaje się robić to, czego potrzebujesz.

This question omawia ten sam problem dla C#.

Aby korzystać ClassData z testów, po prostu zrobić klasa dane wdrażaniu 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) 

Aczkolwiek wymaga to ręcznej kodowania, można ponownie użyć klasy danych, która wydaje się być przydatne w rzeczywiste projekty, w których często przeprowadzamy różne testy na tych samych danych.

+0

Nie mogę sprawić, by to rozwiązanie działało tak, jak jest, ale widzę, dokąd zmierzasz i wierzcie, że jest to "poprawna" odpowiedź. Dziękuję, sprawię, że będzie to poprawne, gdy tylko zacznę działać. –

+0

The seq {[| 3; 4 |] [| 32; 43 |]} nie jest prawidłowym wyrażeniem seq, w końcu użyłem typu MyArrays() = niech wartości = seq [ [| 3; 4 |] // pierwszy testowy przypadek [| 32; 42 |] // 2 przykład testy, itp ] |> Seq.cast interfejs SEQ z element this.GetEnumerator <'T>() = values.GetEnumerator()> System.Collections.Generics.IEnumerator <'T> member this.GetEnumerator() = values.GetEnumerator():> System.Collections.IEnumerator –

+1

@MikeHarris Masz rację w swoich przypuszczeniach, zredagowałem w dwóch brakujących operatorach plonów i typie błędu [I odpowiedź rozszerzająca się na wszystkich to poniżej] –