2017-06-28 2 views
3

J'essaie d'apprendre à utiliser correctement FsCheck et à l'intégrer à Expecto pour le moment. Je peux exécuter des tests de propriétés si j'utilise la configuration par défaut FsCheck, mais lorsque j'essaie d'utiliser mon propre générateur, cela provoque une exception de dépassement de pile.Expecto FsCheck obtient une exception de dépassement de pile lors de la génération de la chaîne

Voici mon générateur

type NameGen() = 
    static member Name() = 
     Arb.generate<string * string> 
     |> Gen.where (fun (firstName, lastName) -> 
      firstName.Length > 0 && lastName.Length > 0 
     ) 
     |> Gen.map (fun (first, last) -> sprintf "%s %s" first last) 
     |> Arb.fromGen 
     |> Arb.convert string id 

Et je suis en train de l'utiliser comme ceci:

let config = { FsCheckConfig.defaultConfig with arbitrary = [typeof<NameGen>] } 

let propertyTests input = 
    let output = toInitials input 
    output.EndsWith(".") 

testPropertyWithConfig config "Must end with period" propertyTests 

L'exception est levée avant qu'il ne soit même dans la fonction Gen.where

Qu'est-ce que est-ce que je fais mal? Merci

Répondre

5

Vous essayez d'utiliser le générateur de chaînes de FsCheck pour redéfinir le fonctionnement de son générateur de chaînes, mais lorsque vous le faites, il s'appelle récursivement jusqu'à épuisement de l'espace de pile. C'est un problème connu: https://github.com/fscheck/FsCheck/issues/109

Est-ce que cette alternative fonctionne?

type NameGen = 
    static member Name() = 
     Arb.Default.NonEmptyString().Generator 
     |> Gen.map (fun (NonEmptyString s) -> s) 
     |> Gen.two 
     |> Gen.map (fun (first, last) -> sprintf "%s %s" first last) 
     |> Arb.fromGen 
3

Vous définissez un nouveau générateur pour la chaîne de type, mais à l'intérieur que vous utilisez le générateur pour string * string, qui utilise le générateur pour string. FsCheck semble malheureusement stocker les générateurs dans un état mutable global (peut-être pour une bonne raison?) Et je pense que cela signifie que le générateur continue de s'appeler lui-même jusqu'à ce que le stack déborde.

Vous pouvez résoudre ce problème en définissant le générateur pour un type de wrapper personnalisé au lieu d'une chaîne simple (voir ci-dessous).

Le prochain problème que vous rencontrerez sera des exceptions de référence nulles. La chaîne générée initiale peut être null et vous essayez d'accéder à la propriété .Length. Cela peut être résolu à l'aide de la fonction String.length à la place, ce qui renvoie 0 pour null.

Avec ces changements que votre générateur ressemble à ceci:

type Name = Name of string 

type NameGen() = 
    static member Name() = 
     Arb.generate<string * string> 
     |> Gen.where (fun (firstName, lastName) -> 
      String.length firstName > 0 && String.length lastName > 0 
     ) 
     |> Gen.map (fun (first, last) -> sprintf "%s %s" first last) 
     |> Arb.fromGen 
     |> Arb.convert Name (fun (Name n) -> n) 

Et votre propriété a besoin d'une légère modification:

let propertyTests (Name input) = 
    let output = toInitials input 
    output.EndsWith(".")