Accueil🇫🇷Chercher

Visual T sharp

Visual T# (prononcé [tiː.ʃɑːp]) est un environnement de développement gratuit de tests unitaires intégré à Visual Studio, mais peut s'utiliser indépendamment. Il comprend :

  • T# : un langage de programmation destiné aux tests unitaires. T# est basé sur le langage C# v2, ce qui le rend très naturel à utiliser par les développeurs .NET. De nouveaux mots-clés y ont été introduits pour simplifier l'écriture de tests. Il met beaucoup d'emphase sur la définition de l'intention des tests ;
  • Des outils pour compiler les tests, les exécuter, les tester et pour facilement naviguer parmi les tests, même pour un grand nombre de tests.
Visual T#
Date de première version 2009
Paradigme Compatible C# donc structuré, imperatif, orienté objet
Auteur Pretty Objects Computers inc.
Typage statique, fort, nominatif

Avantages

T# est un langage de programmation pour Microsoft .NET, compatible avec C# v2 (sauf en ce qui concerne le code non "managed"), et offre les avantages suivants par rapport aux solutions NUnit ou Visual Studio Team Test :

  • utilisation des bonnes pratiques : identifie bien les trois parties (préparation, exécution, vérification) ;
  • identification rapide des problèmes : les tests mal construits sont séparés des tests qui ont échoué ;
  • facilité d'écriture des validations : un seul mot clé assert pour traiter tous les cas ;
  • efficacité dans le débogage : les tests concernant la déclaration que vous travaillez sont exécutés sans savoir où vous les avez déclarés ;
  • utilisation de différents contextes : ré-exécutez mêmes les tests pour différents contextes sans les réécrire ;
  • indication des tests logiques manquants : les tests logiques manquants sont indiqués dès la compilation du code de test ;
  • simplicité d'écriture : chaque vérification s'effectue en 1 ligne de code (même pour les événements, les exceptions...), utilisez des vérifications relatives.

Le langage

Voici un exemple d'un test minimal, écrit en T# :

testclass
{
  test
  {
    Calculatrice calc = new Calculatrice();
    runtest double somme = calc.Ajouter(1, 2);
    assert somme == 3;
  }
}

Bonnes pratiques pour les tests unitaires

T# est complètement orienté bonnes pratiques.

Structure d'un test

Un test unitaire est toujours composé de trois parties :

  • Préparation : création des instances nécessaires à l'exécution du test
  • Exécution : une seule instruction pour utiliser la déclaration à tester dans un contexte donné
  • Vérification : vérification que les résultats attendus (directement ou indirectement) sont bien là.

La partie la plus importante étant Exécution, c'est pour elle que vous écrivez le test.

T# identifie clairement la partie Exécution en faisant commencer l'instruction par le mot clé runtest. La préparation est donc naturellement tout ce qui se trouve avant runtest, et la vérification après runtest.

Vérifications

La partie vérification s'assure que tous les effets (par exemple : retour de fonction, changements des paramètres, modification d'instance, de déclarations statiques, de fichiers, bases de données...) escompté lors de l'utilisation de la déclaration se sont bien effectués comme prévu.

T# n'offre qu'un seul mot-clé pour cela : assert. Le message est automatiquement généré à partir du code source. Ainsi, si le test précédent échoue (si la fonction Ajouter est mal codée en retournant toujours 0), le test échouera avec le message d'erreur suivant : "Expected: somme == 3 but was: 0 == 3". Il est ainsi très rapide de déterminer que la somme vaut 0, mais que l'on s'attendait à 3. Cette façon de générer le message d'erreur le rend plus proche du code source (donc plus facile de faire le lien avec le code quand l'erreur apparait) et décompose les différentes valeurs impliquées (si plusieurs variables étaient impliquées, on saurait alors la valeur de chacune des variables et non pas de l'expression finale), rendant le déboguage beaucoup plus facile.

De plus, les conversions naturelles du langage de programmation sont utilisées (somme est un double, 3 est un entier). Ainsi, pas de problème pour comparer les deux contrairement à Visual Studio Team Test pour lequel vous devez écrire : Assert.AreEqual(3.0, somme); pour faire la même vérification!

Quatre états pour un test

Le test étant du code, il peut lui aussi échouer. Contrairement aux concurrents, T# sait exactement l'instruction qui teste réellement (runtest). Ainsi, il est en mesure de déterminer si l'échec du test s'effectue avant ou après cette instruction :

  • Si le test échoue avant l'instruction runtest, c'est que l'on n'arrive pas à se placer dans le contexte voulu pour tester notre intention. Le test n'est pas bon.
  • Si le test échoue après l'instruction runtest, c'est que le code testé ne fait probablement pas la bonne chose. Le code testé n'est pas bon.

T# a donc quatre états pour un test :

  • Passed : le test a réussi.
  • Ignored : le test est temporairement ignoré.
  • Failed : le test a échoué, le code testé n'est pas bon.
  • Invalid : le test a échoué, le contexte pour tester n'est pas bon, il est donc impossible de vérifier si le code testé est correct ou non.

Afin de profiter de cette différence et de rendre le test très clair, utilisez des assert avant l'instruction runtest :

testclass
{
  test
  {
    Produit prod = new Produit("T#", 123);
    runtest prod.Prix = 0;
    assert prod.Prix == 0;
  }
}

Dans cet exemple, on veut rendre gratuit T#. Le test passe. Le code set de la propriété Prix est donc correct. Vraiment? Mais si ni le constructeur, ni le set de la propriété Prix ne sont codés... le test passe!

Un bon test pour le changement du prix est donc :

testclass
{
  test
  {
    Produit prod = new Produit("T#", 123);
    assert prod.Prix != 0;
    runtest prod.Prix = 0;
    assert prod.Prix == 0;
  }
}

Maintenant, ce cas est exclu. Si le constructeur n'initialise pas la propriété Prix, le premier assert échouerait et, comme il est avant l'instruction runtest, le test est 'Invalid' et non pas échoué! De plus, d'un point de vue logique d'affaires, on voit bien que mettre le prix à 0 le fait passer d'une valeur non nulle à nulle et donc qu'un prix nul est acceptable.

Quoi tester ?

T# incite à dire ce qui est testé, non pas par des noms de classes et méthodes les plus appropriés possibles, mais en l'indiquant clairement. Ainsi, le test précédent devrait s'écrire :

testclass for Produit
{
  test Prix set
  {
    Produit prod = new Produit("T#", 123);
    assert prod.Prix != 0;
    runtest prod.Prix = 0;
    assert prod.Prix == 0;
  }
}

Les avantages sont les suivants :

  • Les éléments testés sont validés par le compilateur.
  • On retrouve facilement les tests qui testent une déclaration précise
  • Il est possible d'exécuter tous les tests d'une déclaration sans savoir où ils sont! Dans Visual Studio, placez votre curseur d'édition dans le code d'une déclaration et utiliser le menu contextuel pour exécuter 'Run T# Tests'! Particulièrement intéressant lorsque l'on retravaille une déclaration sans changer ses objectifs (refactoring)
  • Il est possible d'extraire tous les tests d'une déclaration pour les présenter graphiquement dans le 'Tests Runner'.

Contextes

Comme pour tout système de test, il y a beaucoup de redondances dans l'écriture des tests. En effet, il est nécessaire d'avoir plusieurs tests pour chaque déclaration d'une classe et, généralement, une classe possède plusieurs déclarations. Dans tous ces tests, il sera nécessaire de créer une instance de la classe à tester.

Tous les systèmes de test proposent une méthode à appeler avant tout test et une à appeler après tout test. T#, lui, ne propose qu'une seule méthode.

Cela procure ainsi les avantages suivants :

  • utilisation de variables locales et non pas systématiquement d'instances.
  • utilisation d'instructions using, try...catch, try...finally etc pour encadrer tout test
  • répétition du mot clé runtest pour rouler les tests plusieurs fois! directement ou dans une boucle. Particulièrement utile pour exécuter tous les tests dans différents contextes

Contexte de chaque test

La forme la plus simple de contexte est le context de chaque test. C'est celle utilisée par défaut.

Les tests sont exécutés, mais pas directement. Le contexte, introduit par une méthode déclarée par le mot clé testcontext, est appelée pour chaque test. Le mot clé runtest indique l'endroit où le test doit réellement s'exécuter.

Ainsi, dans notre exemple, nous voulons créer l'instance avec une seule ligne de code, mais il faut créer une instance pour chaque test :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set // valeur minimale
  {
    assert prod.Prix != 0;
    runtest prod.Prix = 0;
    assert prod.Prix == 0;
  }
  test Prix set // valeur valide quelconque
  {
    assert prod.Prix != 12;
    runtest prod.Prix = 12;
    assert prod.Prix == 12;
  }
}

Différents niveaux de contexte

En T#, le contexte se situe à trois niveaux :

  1. test : le code du contexte est effectué pour chaque test. Le test lui-même étant effectué à l'appel de runtest dans le contexte. Contexte par défaut.
  2. testclass : le code du contexte est effectué pour la classe de test. Les tests de la classe de test étant effectués à l'appel de runtest dans le contexte.
  3. testassembly : le code du contexte est effectué pour l'ensemble des classes de test de l'assemblage. Les tests étant effectué à l'appel de runtest dans le contexte.

Dans cet exemple, les tests seront exécutés deux fois, sans avoir à les écrire deux fois :

  1. pour une connexion vers une base de données SQL Server.
  2. pour une connexion vers une base de données Oracle.
testclass
{
  IDbConnection connexion;
  testcontext
  {
    testclass
    {
      connexion = new SQLConnection(...);
      runtest;
      connexion = new OracleConnection(...);
      runtest;
    }
  }
  ...
}

Quel cas tester ?

Lors de l'écriture de tests unitaires, le problème le plus classique est : « Quel cas dois-je tester ? ». En effet, une même déclaration doit être testée dans différents cas. Un des exemples précédents traitait du prix d'un produit représenté par une propriété. Combien faut-il de tests et quels sont ces tests dans un tel cas?

Dans les systèmes de tests classiques, c'est encore une fois le nom du test qui dit quel cas est testé (ou un commentaire, comme dans notre exemple précédent). Cela donne des noms souvent très longs et pas nécessairement clairs... ni mis à jour.

T# introduit un nouveau mot clé pour exprimer le cas testé : when suivi d'un cas à tester. Ainsi, l'exemple des tests du prix d'un produit devrait être :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.IsMin
  {
    assert prod.Prix != 0;
    runtest prod.Prix = 0;
    assert prod.Prix == 0;
  }
  test Prix set
    when MinIncluded.IsAboveMin
  {
    assert prod.Prix != 12;
    runtest prod.Prix = 12;
    assert prod.Prix == 12;
  }
}

Cas manquants

En fait, ce qui suit le mot clé when est un cas parmi plusieurs fonctionnant ensemble, décrits par un critère. Dans notre exemple, le critère est MinIncluded qui combine deux cas normaux (IsAboveMin et IsMin) et un cas d'erreur (BelowMinCase).

Il suffit donc d'identifier qu'un prix de produit a une valeur minimale (0) pour identifier qu'il faut tester selon le critère MinIncluded. Ce critère définissant trois cas, il va falloir écrire 3 tests pour tester cette propriété, un pour chaque cas défini dans le critère.

Pour l'instant, nous n'avons que deux cas définis (les cas normaux). Dès la compilation, T# indique les cas manquants : MinIncludedCriteria.BelowMinCase.

Expressions de cas

En réalité, après un when, une expression de cas est utilisée. Cette expression peut être un cas simple d'un critère ou une combinaison de critères.

Les opérateurs suivants existent :

  • && (et logique) : combine toutes les possibilités entre deux critères, sachant que seules les cas normaux sont combinables, les cas d'erreur devant être testés séparément.
  • || (ou logique) : regroupe deux cas dans un même test. A priori ce n'est pas une bonne idée, mais cela peut être nécessaire d'exprimer la situation d'un même test exécutant plusieurs tests avec paramètres.
  • => (implication) : combine le cas de gauche avec les différents cas du critère de droite. Utile lorsque toutes les combinaisons ne sont pas logiques.

Enfin, lorsqu'un cas n'a pas de sens, il est possible de demander à ne pas le prendre en compte en déclarant le cas !when. Dans ce cas, le test ne doit pas avoir de code, donc pas d'accolades, seulement un point-virgule.

Critères

Il existe déjà beaucoup de critères dans la bibliothèque de T#, mais cela ne peut couvrir tout vos besoins. Il est alors très facile de créer les vôtres.

Un critère est comme un type énuméré, mais défini par le mot clé criteria et non pas enum. Les cas d'erreur sont repérés en ajoutant l'attribut [Error] au cas en question.

La convention veut que :

  1. le nom du critère se termine par "Criteria", même s'il n'est pas nécessaire de l'ajouter lors de l'utilisation (comme "Attribute" pour les attributs)
  2. un cas normal se présente avec un 'Is' ou 'Has'
  3. un cas d'erreur se termine par 'Case'

Ainsi, la déclaration de MinIncludedCriteria est la suivante :

public criteria MinIncludedCriteria
{
  [Error]
  BelowMinCase,
  IsMin,
  IsAboveMin,
}

Tester les exceptions

Comme nous l'avons vu avec les critères dans le paragraphe précédent, il est nécessaire de non seulement tester les cas normaux, mais aussi les cas d'erreur.

Généralement, un cas d'erreur est rapporté par une exception.

Il faut donc pouvoir tester les exceptions.

Tester qu'une exception est lancée

T# vérifie les exceptions comme toute autre vérification :

  • en une seule ligne
  • avec le mot assert, mais suivi de thrown et du nom de l'exception

Les avantages sont les suivants :

  1. garantie que l'exception est bien dans la partie runtest (donc dans le code d'affaires) et non pas avant (préparation) ou après (vérification)!
  2. ajout possible d'autres assertions pour garantir que rien d'inattendu n'a été fait avant l'exception (comme changer le prix avant de déclencher l'exception par exemple!)

Ainsi, dans l'exemple précédent, il est nécessaire de tester le cas où le prix affecté au produit est négatif. Comme cela n'a pas de sens, la propriété devrait générer une exception ArgumentOutOfRangeException. Testez-le ainsi :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.BelowMinCase
  {
    runtest prod.Prix = -12;
    assert thrown ArgumentOutOfRangeException;
    assert prod.Prix == 123; // Le prix n'a pas changé
  }
  ...
}

Tester complètement une exception

En fait, cela va simplement vérifier que l'exception est bien générée dans l'instruction runtest. Ce qui est déjà bien. Cependant, il serait bon de pouvoir valider le message d'erreur par exemple.

L'instruction assert thrown <type-exception> peut aussi être suivi d'un nom de variable, comme dans une instruction catch, et d'un bloc de code pour faire autant de vérifications voulues lorsque l'exception est déclenchée. Accédez alors normalement à cette variable pour vérifier tout ce que vous voulez.

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.BelowMinCase
  {
    runtest prod.Prix = -12;
    assert thrown ArgumentOutOfRangeException e
    {
      assert e.Message == "Un prix ne peut être négatif!";
    }
    assert prod.Prix == 123; // Le prix n'a pas changé
  }
  ...
}

Vérifier les changements

Le problème d'utiliser des contextes est que celui-ci peut se trouver physiquement loin du test que l'on travaille, et lorsqu'il est changé, peut avoir des conséquences sur l'ensemble des tests.

Ainsi, dans l'exemple précédent, si le produit créé a maintenant un prix de 100 au lieu de 123, l'instruction assert prod.Prix == 123; échoue car le prix sera de 100!

L'idéal serait de faire des tests relatifs : conserver la valeur initiale de prod.Prix dans une variable locale, puis l'utiliser dans la vérification. Le problème est que cela fait plus de code à écrire.

T# offre la possibilité d'écrire des vérifications relatives en une seule ligne de code.

Vérifier la constance d'une expression

La forme la plus simple de vérification relative est celle de la constance d'une expression.

T# offre une nouvelle forme de l'instruction assert : assert !changed <expression>

L'expression sera évalué avant l'instruction runtest, et sa valeur conservée, pour être de comparée par égalité au moment du assert en question.

Ainsi, dans notre exemple, plutôt que de vérifier que le prix du produit est bien 123, il serait préférable de vérifier que le prix n'a pas changé :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.BelowMinCase
  {
    runtest prod.Prix = -12;
    assert thrown ArgumentOutOfRangeException e
    {
      assert e.Message == "Un prix ne peut être négatif!";
    }
    assert !changed prod.Prix;
  }
  ...
}

Vérifier la constance d'un objet

La forme la plus sophistiquée de vérification relative est celle de la constance d'un objet. En effet, qui dit que notre code d'affaires n'a pas modifié l'objet avant que de lancer l'exception?

Dans l'instruction assert !changed <expression>, l'expression peut référencer un objet et se terminer par :

  1. .* : indique au compilateur T# de vérifier chacune des propriétés publique de l'objet en question. Donc que l'objet n'a pas changé en apparence.
  2. .-* : indique au compilateur T# de vérifier chacune des variables (quel que soit le niveau d'encapsulation) de l'objet en question. Donc que l'objet n'a vraiment pas changé.

Note : l'opérateur .- est semblable à l'opérateur . si ce n'est qu'il accède à n'importe quelle déclaration, privée ou non.

Ainsi, dans notre exemple, plutôt que de vérifier que le prix du produit n'a pas changé, il serait préférable de vérifier que l'objet prod n'a pas changé :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.BelowMinCase
  {
    runtest prod.Prix = -12;
    assert thrown ArgumentOutOfRangeException e
    {
      assert e.Message == "Un prix ne peut être négatif!";
    }
    assert !changed prod.-*; // le produit n'a pas changé
  }
  ...
}

Vérifier un changement

Sur le même principe, vérifiez qu'un changement a été apporté par une assertion relative.

Une assertion relative de changement s'effectue avec l'instruction assert changed <affectation>.

L'affectation se présente sous 3 formes :

  1. élément = expression
  2. élément op= expression : ainsi, élément += 1 est équivalent à élément = élément + 1
  3. élément++ ou element-- : ainsi, élément++ est équivalent à élément += 1 donc à élément = élément + 1

La partie de droite est évaluée avant l'instruction runtest, et conservée, pour être comparée par égalité à la partie de gauche sur le assert correspondant. Ainsi, assert changed élément++ n'incrémente pas élément, mais vérifie que la valeur d'élément ajoutée de 1 avant l'instruction runtest est égale à la valeur d'élément après l'instruction runtest. Ou tout simplement, que l'instruction runtest à bien fait augmenter de 1 la valeur d'élément. Ainsi, c'est l'expression équivalente à une affectation comme précisé, mais seulement des accès en lecture sont effectués. Il est donc possible de l'utiliser avec des propriétés en lecture seule.

Si nous reprenons notre exemple avec notre classe Produit, on pourrait ajouter une classe Inventaire (une collection de Produit)qui aurait donc une méthode Add(Produit) et une propriété Count.

Le test de cette méthode serait :

testclass for Inventaire
{
  Inventaire inventaire;
  testcontext
  {
    inventaire = new Inventaire();
    runtest;
  }
  test Add( Produit p )
  {
    Product produit = new Product( "T#", 123 );
    runtest inventaire.Add( produit );
    assert changed inventaire.Count++; // un produit a été ajouté
    assert inventaire[ inventaire.Count - 1 ] == produit; // le produit a été ajouté en dernier
  }
  ...
}

Tester les événements

En dehors des exceptions, les événements non plus ne sont pas faciles à tester correctement. Il n'existe aucune facilité fournie par les systèmes de tests existants.

T# offre une nouvelle fois l'instruction assert mais avec le mot clé raised.

Par exemple, une classe implémentant INotifyPropertyChanged doit déclencher l'événement PropertyChanged si une propriété est changée. Mais, elle ne devrait pas déclencher l'événement si la valeur affectée est la même que celle actuelle!

Note : Ce cas étant classique, nous T# fournit déjà le critère NotifyPropertyChangedCriteria avec trois cas :

  1. HasNoSubscriber : test normal, cas représenté dans les exemples précédents.
  2. HasSubscribersSetSameValue : cas représenté dans le prochain paragraphe.
  3. HasSubscribersSetOtherValue : cas représenté dans les paragraphes suivants.

Vérifier le non-déclenchement d'un événement

La forme la plus simple est la vérification du non déclenchement d'un événement.

En T#, la vérification du non-déclenchement d'un événement s'effectue comme toujours en une ligne de code : assert !raised <événement>;

Le compilateur T# génère une variable d'instance et une méthode compatible avec la signature de l'événement. Dans le test, la variable est initialisée à faux, la méthode est enregistrée (+=) auprès de l'événement avant l'instruction runtest et désenregistrée (-=) après l'instruction runtest. La méthode générée va réinitialiser la variable à vrai. L'instruction runtest !raised va vérifier que la variable est toujours à faux.

En supposant que notre classe Produit supporte l'interface INotifyPropertyChanged, nous devrions avoir le test suivant :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.IsAboveMin && NotifyPropertyChanged.HasSubscribersSetSameValue
  {
    runtest prod.Prix = prod.Prix;
    assert !changed prod.-*;
    assert !raised prod.PropertyChanged;
  }
  ...
}

Vérifier le déclenchement d'un événement

La forme la plus simple de vérification du déclenchement d'un événement vérifie seulement que l'événement est déclenché.

Comme toujours, T# le vérifie en une seule ligne de code : assert raised <événement>;

Le compilateur T# génère exactement les mêmes choses que pour assert !changed, mais vérifie que la variable est à vrai.

Ainsi, dans notre exemple, nous devrions avoir :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.IsAboveMin && NotifyPropertyChanged.HasSubscribersSetOtherValue
  {
    assert prod.Prix != 12;
    runtest prod.Prix = 12;
    assert prod.Prix == 12;
    assert raised prod.PropertyChanged;
  }
  ...
}

Vérifier complètement un événement

L'inconvénient de procéder comme dans le chapitre précédent, c'est que cela prouve simplement que l'événement s'est déclenché, pas que :

  1. les paramètres associés sont correctement passés. Selon notre exemple, le sender est-il bien le produit modifié? Le second paramètre fait-il bien référence à la bonne propriété?
  2. l'état de l'objet est correct au moment de l'événement. Selon notre exemple, le produit est-il bien déjà avec sa nouvelle valeur de prix quand l'événement est déclenché?

Une forme beaucoup plus sophistiquée de test des événements existe : assert raised <événement>(<paramètres>) { <vérifications> } Où :

  • <paramètre> représente les paramètres correspondant à la signature de l'événement.
  • <vérifications> représente les assertions à vérifier dans la méthode réagissant à l'événement.

Ainsi, le même tests que dans le chapitre précédent, mais complet serait :

testclass for Produit
{
  Produit prod;
  testcontext
  {
    prod = new Produit("T#", 123);
    runtest;
  }
  test Prix set
    when MinIncluded.IsAboveMin && NotifyPropertyChanged.HasSubscribersSetOtherValue
  {
    assert prod.Prix != 12;
    runtest prod.Prix = 12;
    assert prod.Prix == 12;
    assert raised prod.PropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      assert sender == prod;
      assert e.PropertyName == "Prix";
      assert prod.Price == 12;
    }
  }
  ...
}

Tester avec les 'Code Snippets'

Visual Studio offre la possibilité d'utiliser des 'Code Snippets' dans les langages de Microsoft. Nous avons aussi ajouté des 'Code Snippets' pour Visual T# (25 dans la version 1.0).

Ainsi, rien de plus facile pour générer vos tests :

  1. identifier quel critère s'applique.
  2. utiliser le 'Code Snippet' correspondant.
  3. remplacer les quelques paramètres du 'Snippets'.

Dans notre exemple, nous voulons tester le prix d'un produit.

Nous avons déjà établi que les critères à utiliser sont :

  1. MinIncludedCriteria : car le prix peut être nul (valeur minimale), mais pas négatif.
  2. NotifyPropertyChangedCriteria : car la classe Produit implémente l'interface INotifyPropertyChanged.

Comme par hasard, il existe le 'Code Snippet' NotifyMinIncluded qui va générer d'un seul coup le code.

Il ne reste plus qu'à indiquer les informations suivantes :

  1. le nom de la propriété à tester (Prix).
  2. le nom de l'instance à utiliser, c'est-à-dire celle créé dans le contexte (prod).
  3. la valeur minimale acceptable (0).
  4. la valeur au-dessus du minimum à utiliser (12).
  5. la valeur en dessous du minimum à utiliser (-12).

Et voici le code généré :

    test Prix set
      when MinIncluded.IsMin && NotifyPropertyChanged.HasNoSubscriber
    {
      assert prod.Prix != 0;
      runtest prod.Prix = 0;
      assert prod.Prix == 0;
    }
    test Prix set
      when MinIncluded.IsMin && NotifyPropertyChanged.HasSubscribersSetSameValue
    {
      assert prod.Prix == 0;
      runtest prod.Prix = 0;
      assert !changed prod.Prix;
      assert !raised prod.PropertyChanged;
    }
    test Prix set
      when MinIncluded.IsMin && NotifyPropertyChanged.HasSubscribersSetOtherValue
    {
      assert prod.Prix != 0;
      runtest prod.Prix = 0;
      assert prod.Prix == 0;
      assert raised prod.PropertyChanged( object sender, PropertyChangedEventArgs args )
      {
        assert sender == prod;
        assert args.PropertyName == "Prix";
        assert prod.Prix == 0;
      }
    }
    test Prix set
      when MinIncluded.IsAboveMin && NotifyPropertyChanged.HasNoSubscriber
    {
      assert prod.Prix != 12;
      runtest prod.Prix = 12;
      assert prod.Prix == 12;
    }
    test Prix set
      when MinIncluded.IsAboveMin && NotifyPropertyChanged.HasSubscribersSetSameValue
    {
      assert prod.Prix == 12;
      runtest prod.Prix = 12;
      assert !changed prod.Prix;
      assert !raised prod.PropertyChanged;
    }
    test Prix set
      when MinIncluded.IsAboveMin && NotifyPropertyChanged.HasSubscribersSetOtherValue
    {
      assert prod.Prix != 12;
      runtest prod.Prix = 12;
      assert prod.Prix == 12;
      assert raised prod.PropertyChanged( object sender, PropertyChangedEventArgs args )
      {
        assert sender == prod;
        assert args.PropertyName == "Prix";
        assert prod.Prix == 12;
      }
    }
    test Prix set
      when MinIncluded.BelowMinCase
    {
      runtest prod.Prix = -12;
      assert thrown ArgumentOutOfRangeException;
      assert !changed prod.-*;
    }

Liens externes

Visual T# est téléchargeable gratuitement sur : Forum Visual T#

Cet article est issu de wikipedia. Text licence: CC BY-SA 4.0, Des conditions supplémentaires peuvent s’appliquer aux fichiers multimédias.