2017-08-01 2 views
3

Je teste avec FsCheck et NUnit dans VisualStudio. Le problème est actuellement: j'ai réussi à générer des graphes aléatoires (pour tester certaines fonctionnalités de graphes) mais quand un test échoue, FsCheck crache le graphe entier et n'utilise pas ToString donc il vide littéralement la liste brute des enregistrements et vous Je ne vois rien ici.Personnaliser la sortie FsCheck

Aussi, j'aurais besoin non seulement du graphe d'entrée pour l'inspection, mais aussi d'autres données que je crée lors de l'exécution d'une propriété.

Alors, comment puis-je changer le comportement de sortie de FsCheck pour

  • fait appeler ma méthode ToString sur le graphique d'entrée
  • sortie plus d'informations

lorsqu'un test échoue?

EDIT: Voici ma configuration de test actuelle.

module GraphProperties 

open NUnit.Framework 
open FsCheck 
open FsCheck.NUnit 

let generateRandomGraph = 
    gen { 
     let graph: Graph<int,int> = Graph<_,_>.Empty() 
     // fill in random nodes and transitions... 
     return graph 
    } 

type MyGenerators = 
    static member Graph() = 
     {new Arbitrary<Graph<int,int>>() with 
      override this.Generator = generateRandomGraph 
      override this.Shrinker _ = Seq.empty } 

[<TestFixture>] 
type NUnitTest() = 
    [<Property(Arbitrary=[|typeof<MyGenerators>|], QuietOnSuccess = true)>] 
    member __.cloningDoesNotChangeTheGraph (originalGraph: Graph<int,int>) = 
     let newGraph = clone originalGraph 
     newGraph = originalGraph 
+2

Comment allez-vous vos affirmations? Il y a différentes manières d'affirmer même en utilisant NUnit pour exécuter des tests. Cela aiderait à fournir un exemple de test, ou même la dernière ligne. – TheQuickBrownFox

+0

Je viens d'ajouter un exemple de code. Le plus difficile était de faire fonctionner le générateur. Pour la propriété elle-même, je reçois une entrée aléatoire, appelle la méthode testée avec cette entrée et renvoie un booléen (le résultat de la comparaison dans ce cas). –

+0

Est-ce que 'Graph <_,_>' un enregistrement ou une classe? Pouvez-vous fournir la définition (peut-être épurée) pour cela? Et la sortie réelle que vous voyez? – TheQuickBrownFox

Répondre

1

FsCheck utilise sprintf "%A" pour convertir les paramètres de test en chaînes dans la sortie de test, donc ce que vous devez faire est de contrôler la manière dont les types sont mis en forme par le formatter %A. Selon How do I customize output of a custom type using printf?, la façon de le faire est avec le StructuredFormatDisplay attribute. La valeur de cet attribut doit être une chaîne au format PreText {PropertyName} PostText, où PropertyName devrait être une propriété (pas une fonction!) Sur votre type. Par exemple, disons que vous avez une structure arborescente avec des informations compliquées dans les feuilles, mais pour vos tests, vous n'avez besoin que de connaître le nombre de feuilles, et non leur contenu. Donc, vous commencez avec un type de données comme celui-ci:

// Example 1 
type ComplicatedRecord = { ... } 
type Tree = 
    | Leaf of ComplicatedRecord 
    | Node of Tree list 
    with 
     member x.LeafCount = 
      match x with 
      | Leaf _ -> 1 
      | Node leaves -> leaves |> List.sumBy (fun x -> x.LeafCount) 
     override x.ToString() = 
      // For test output, we don't care about leaf data, just count 
      match x with 
      | Leaf -> "Tree with a total of 1 leaf" 
      | Node -> sprintf "Tree with a total of %d leaves" x.LeafCount 

Maintenant, ce n'est pas ce que vous voulez. pas a déclaré un format personnalisé %A déclaré, donc FsCheck (et toute autre chose qui utilise sprintf "%A" pour le formater) finira par sortir toute la structure compliquée de l'arbre et toutes ses données de feuilles non pertinentes au test. Pour que FsCheck produise ce que vous voulez voir, vous devez configurer une propriété , pas une fonction (ToString ne fonctionnera pas à cette fin) qui affichera ce que vous voulez voir. .: par exemple

// Example 2 
type ComplicatedRecord = { ... } 
[<StructuredFormatDisplay("{LeafCountAsString}")>] 
type Tree = 
    | Leaf of ComplicatedRecord 
    | Node of Tree list 
    with 
     member x.LeafCount = 
      match x with 
      | Leaf _ -> 1 
      | Node leaves -> leaves |> List.sumBy (fun x -> x.LeafCount) 
     member x.LeafCountAsString = x.ToString() 
     override x.ToString() = 
      // For test output, we don't care about leaf data, just count 
      match x with 
      | Leaf -> "Tree with a total of 1 leaf" 
      | Node -> sprintf "Tree with a total of %d leaves" x.LeafCount 

NOTE: Je n'ai pas testé en F #, juste tapé dans la zone de commentaire Stack Overflow - il est donc possible que j'ai foiré la partie ToString(). (Je ne me souviens pas, et ne peux pas trouver avec un rapide Google, si les remplacements doivent être après ou avant le mot-clé with). Mais je sais que l'attribut StructuredFormatDisplay est ce que vous voulez, parce que je l'ai utilisé moi-même pour obtenir une sortie personnalisée de FsCheck. Par ailleurs, vous pouvez également avoir défini un attribut StructuredFormatDisplay sur le type d'enregistrement compliqué dans mon exemple. Par ailleurs, vous pouvez également définir un attribut StructuredFormatDisplay.Par exemple, si vous avez un test où vous vous souciez de la structure de l'arbre, mais pas sur le contenu des feuilles, vous rédigeriez comme:

// Example 3 
[<StructuredFormatDisplay("LeafRecord")>] // Note no {} and no property 
type ComplicatedRecord = { ... } 
type Tree = 
    | Leaf of ComplicatedRecord 
    | Node of Tree list 
    with 
     member x.LeafCount = 
      match x with 
      | Leaf _ -> 1 
      | Node leaves -> leaves |> List.sumBy (fun x -> x.LeafCount) 
     override x.ToString() = 
      // For test output, we don't care about leaf data, just count 
      match x with 
      | Leaf -> "Tree with a total of 1 leaf" 
      | Node -> sprintf "Tree with a total of %d leaves" x.LeafCount 

Maintenant, tous vos ComplicatedRecord cas, peu importe leur contenu, sera apparaît comme le texte LeafRecord dans votre sortie, et vous serez mieux en mesure de vous concentrer sur la structure arborescente - et il n'était pas nécessaire de définir un attribut StructuredFormatDisplay sur le type Tree.

Cette solution n'est pas idéale, car vous devrez peut-être ajuster l'attribut StructuredFormatDisplay de temps à autre, selon les besoins des différents tests que vous exécutez. (Pour certains tests, vous pouvez vous concentrer sur une partie des données de feuille, pour d'autres, vous voulez ignorer complètement les données de feuille, etc.). Et vous voudrez probablement retirer l'attribut avant de passer à la production. Mais jusqu'à ce que FsCheck acquière une fonction «Donnez-moi une fonction pour mettre en forme les données de test ayant échoué avec le paramètre« config », c'est le meilleur moyen de mettre vos données de test au format dont vous avez besoin.

+0

Merci, maintenant je peux imprimer mes graphiques comme je le dois. Il y a un bit restant. Vous avez mentionné qu'il n'y a aucun moyen de passer à la fonction de sortie de FsCheck. N'y a-t-il vraiment aucun moyen d'hériter et d'outrepasser la fonctionnalité (oui, c'est la mauvaise pensée OO)? Et cela signifie-t-il qu'en ce qui concerne la deuxième puce de ma question, je n'ai aucune chance de produire par ex. le graphique généré ET son clone lorsque le test échoue dans mon exemple? –

+0

Si vous regardez le [code source FsCheck] (https://github.com/fscheck/FsCheck/blob/master/src/FsCheck/Runner.fs), vous verrez que la fonction 'argumentsToString' est celle appelle 'sprintf"% A "', et il est appelé à partir de 'onFailureToString'. Vous pouvez réellement implémenter vos propres coureurs de test en implémentant l'interface 'IRunner', donc je n'avais pas raison de dire que c'était le seul moyen. C'est certainement le moyen le plus simple, cependant. – rmunn

+0

Et si vous voulez sortir le graphe original et son clone, la façon la plus simple de le faire serait dans votre test: 'let result = (originalGraph = clonedGraph); si pas de résultat, alors printfn "Graphique original:% A" originalGraph; printfn "Graphique cloné:% A" clonedGraph; résultat ». Quelque chose de simple comme ça. – rmunn