2016-12-02 10 views
5

Myślałem, że nadszedł czas, aby wypróbować FsCheck, ale okazuje się trudniejsze niż myślałem. Istnieje wiele dokumentacji na temat Arb, generatorów i tak dalej, ale nie ma żadnych wskazówek, jak zastosować tę wiedzę. Albo po prostu nie rozumiem.Jak używać FsCheck do generowania liczb losowych jako danych wejściowych do testów opartych na właściwościach?

Co może utrudnić zrozumienie, to to, że relacja między testami, właściwościami, generatorami, arbitrami, zmniejszaniem i, w moim przypadku, losowością (niektóre testy automatycznie generują losowe dane, inne nie) jest dla mnie niejasna . Nie mam tła Haskella, więc to też niewiele pomaga.

Teraz pytanie: jak generować losowe liczby całkowite?

Mój scenariusz testu można wyjaśnić na właściwości mnożenia, powiedzmy rozdzielność:

static member ``Multiplication is distributive`` (x: int64) y z = 
    let res1 = x * (y + z) 
    let res2 = x * y + x * z 

    res1 = res2 

// run it: 
[<Test>] 
static member FsCheckAsUnitTest() = 
    Check.One({ Config.VerboseThrowOnFailure with MaxTest = 1000 }, ``Multiplication is distributive``) 

Kiedy uruchomić to z Check.Verbose lub integracji NUnit, mam sekwencje testowe jak:

0: 
(-1L, -1L, -1L) 
1: 
(-1L, -1L, 0L) 
2: 
(-1L, -1L, -1L) 
3: 
(-1L, -1L, -1L) 
4: 
(-1L, 0L, -1L) 
5: 
(1L, 0L, 2L) 
6: 
(-2L, 0L, -1L) 
7: 
(-2L, -1L, -1L) 
8: 
(1L, 1L, -2L) 
9: 
(-2L, 2L, -2L) 

Po 1000 testów nie uzyskał więcej niż 100L. Jakoś sobie wyobrażałem, że to "automatycznie" wybiera losowe liczby równomiernie rozłożone w całym zakresie int64, przynajmniej tak zinterpretowałem dokumentację.

ponieważ nie zacząłem eksperymentować i wpadł głupich rozwiązań takich jak następujących, aby uzyskać wyższe numery:

type Generators = 
    static member arbMyRecord = 
     Arb.generate<int64> 
     |> Gen.where ((<) 1000L) 
     |> Gen.three 
     |> Arb.fromGen 

ale to staje się niezwykle wolno i wyraźnie nie jest właściwe podejście. Jestem pewien, że musi istnieć proste rozwiązanie, którego mi brakuje. Próbowałem z Gen.choose(Int64.MinValue, Int64.MaxValue), ale to obsługuje tylko ints, nie longs (ale nawet z tylko ints nie mogłem go uruchomić działa).

W końcu potrzebuję rozwiązania, które działa dla wszystkich podstawowych typów danych liczbowych, które obejmują ich maksima i miny, ich zera i jedynek, a także losowy wybór z tego, co jest w środku.

+1

Myślę, że to w praktyce ogranicza maksymalną liczbę. do 100, zobacz: [fscheck Q] (http://stackoverflow.com/questions/40591229/fscheck-doesnt-generate-random-enough-data/) – s952163

+1

@ s952163, tak, dlatego próbowałem z 'MaxTest = 1000 ', patrz powyższy kod. Ale to nie pomaga. Być może masz na myśli wartości 'StartTest' i' EndTest', ale ustawienie ich na 'Int32.MinValue/MaxValue' daje efekt, że * wszystkie * permutacje używają' Int32.MinValue' jako stałej wartości. – Abel

Odpowiedz

5

Zgodnie z objaśnieniami w this other FsCheck question domyślne konfiguracje większości funkcji Check mają EndSize = 100. Możesz zwiększyć tę liczbę, ale możesz też, jak sugerujesz, użyć Gen.choose.

Mimo to generator int to intentionally well-behaved. Nie obejmuje to na przykład Int32.MinValue i Int32.MaxValue, ponieważ może to prowadzić do przepełnienia.

FsCheck posiada również generatory, które zapewniają jednolite dystrybucje w całym zakresie: Arb.Default.DoNotSizeInt16, Arb.Default.DoNotSizeUInt64 i tak dalej.

Dla zmiennoprzecinkowych, jest Arb.Default.Float32, które zgodnie z jego dokumentacją, generuje "arbitralnych pływaków, Nan, NegativeInfinity, PositiveInfinity, MAXVALUE, MINVALUE, Epsilon zawarte dość często".

Nie ma jednolitego API dla "just" dowolnej liczby, ponieważ F # nie ma typów liter (jest to coś, co możesz wyrazić w Haskell).

Ponadto, nie jestem pewien, czy typowe ramy testów jednostkowych będą w stanie uruchomić ogólne testy, ale przynajmniej z xUnit.net, można użyć this trick to run generically typed tests.


szczególności, choć można napisać powyższą próbę takiego, używając FsCheck.Xunit:

open FsCheck 
open FsCheck.Xunit 

[<Property>] 
let ``Multiplication is distributive``() = 
    Arb.generate<DoNotSize<int64>> 
    |> Gen.map (fun (DoNotSize x) -> x) 
    |> Gen.three 
    |> Arb.fromGen 
    |> Prop.forAll <| fun (x, y, z) -> 

     let res1 = x * (y + z) 
     let res2 = x * y + x * z 

     res1 = res2 

ten mógłby hipotetycznie niepowodzeniem z przepełnione, ale po uruchomieniu niektórych 1000000 przypadki, I haven” t widziałem, jak to się jeszcze nie udaje.

Generator jednak rzeczywiście wygląda to zbieranie wartości z pełnego zakresu 64-bitowych liczb całkowitych:

> Arb.generate<DoNotSize<int64>> |> Gen.sample 1 10;; 
val it : DoNotSize<int64> list = 
    [DoNotSize -28197L; DoNotSize -123346460471168L; DoNotSize -28719L; 
    DoNotSize -125588489564554L; DoNotSize -29241L; 
    DoNotSize 7736726437182770284L; DoNotSize -2382327248148602956L; 
    DoNotSize -554678787L; DoNotSize -1317194353L; DoNotSize -29668L] 

Zauważ, że mimo tego, że wiążą size argument Gen.sample do 1, to wybiera " dowolnie "duże wartości dodatnie i ujemne.

+0

Dzięki za wskazówki i wyjaśnienia. Ponieważ testuję zachowanie algorytmów, które mogą przyjmować pełny zakres pewnych typów liczbowych, jestem zainteresowany wykorzystaniem tego jako danych wejściowych, a jakiekolwiek przepełnienie lub niedopełnienie byłoby błędem. Nadal nie jestem pewien, jak uzyskać trójdrożne permutacje, które zawierają wszystkie kombinacje wartości "[0; 1; -1; MinValue; MaxValue]" i jeśli z liczby testowej pozostały jakieś resztki, dowolne wartości pośrednie (tak jak styl 'Arb.Default.Float32'). A może to właśnie robi 'Arb.Default.DoNoSitzeXXX'? – Abel

+0

Próbowałem uruchomić test w następujący sposób: 'Check.One ({Config.VerboseThrowOnFailure z Arbitrary = [typeof >]}, MyDistribTestThreeArgs)' (i wariant z '[t; t; t]' if to potrzebne dla trzech argumentów). Jednak każda z metod wyrzuca _ "Nie znaleziono żadnych instancji na typie FsCheck.DoNotSize'1 [[System.Int64 ...." _. Nie mogę użyć 'Arb.Default ...' bezpośrednio, konfiguracja potrzebuje 'Type'. Jestem pewna, że ​​tęsknię za oczywistością, ale co? – Abel

+0

Oh, i 'Arbitrary = Arb.Default.DoNotSizeInt64(). GetType()' rzuca podobny błąd. Które jest nieparzyste, podajesz mu dokładny typ, który również ma domyślny ctor, ale nadal twierdzi, że nie może znaleźć tego typu. – Abel

Powiązane problemy