- TSharp
-
Visual T Sharp
Visual T# Apparu en 2009 Auteur Pretty Objects Computers inc. Paradigme Compatible C# donc structuré, imperatif, orienté objet Typage statique, fort, nominatif 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 dédié 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 mot-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.
Sommaire
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 3 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éboguage : 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'une 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 fonctionAjouter
est mal codée en retournant toujours0
), 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 conversion 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!4 é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 4 é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 leset
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 premierassert
échouerait et, comme il est avant l'instructionruntest
, 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ù letest
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 :
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.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.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 2 fois, sans avoir à les écrire 2 fois :
- pour une connexion vers une base de données SQL Server.
- 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 estMinIncluded
qui combine 2 cas normaux (IsAboveMin
etIsMin
) et 1 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 3 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. À 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 :
- 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)
- un cas normal se présente avec un 'Is' ou 'Has'
- 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 :
- 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)!
- 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 instructioncatch
, 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 duassert
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 :.*
: 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..-*
: 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 :
élément = expression
élément op= expression
: ainsi,élément += 1
est équivalent àélément = élément + 1
élément++
ouelement--
: 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'instructionruntest
est égale à la valeur d'élément après l'instructionruntest
. 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 classeInventaire
(une collection deProduit
)qui aurait donc une méthodeAdd(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ème 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énementPropertyChanged
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 3 cas :- HasNoSubscriber : test normal, cas représenté dans les exemples précédents.
- HasSubscribersSetSameValue : cas représenté dans le prochain paragraphe.
- 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, le méthode est enregistrée (
+=
) auprès de l'événement avant l'instructionruntest
et désenregistrée (-=
) après l'instructionruntest
. La méthode générée va réinitialiser la variable à vrai. L'instructionruntest !raised
va vérifier que la variable est toujours à faux.En supposant que notre classe
Produit
supporte l'interfaceINotifyPropertyChanged
, 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 :
- 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é? - 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énement 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 :
- identifier quel critère s'applique.
- utiliser le 'Code Snippet' correspondant.
- 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 :
MinIncludedCriteria
: car le prix peut être nul (valeur minimale), mais pas négatif.NotifyPropertyChangedCriteria
: car la classe Produit implémente l'interfaceINotifyPropertyChanged
.
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 :
- le nom de la propriété à tester (Prix).
- le nom de l'instance à utiliser, c'est-à-dire celle créé dans le contexte (prod).
- la valeur minimale acceptable (0).
- la valeur au dessus du minimum à utiliser (12).
- 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.-*; }
Lien externe
Visual T# est téléchargeable gratuitement sur : Forum Visual T#
Catégories : Langage de programmation | Langage orienté objet | .NET Framework | Compilateur | Test logiciel
Wikimedia Foundation. 2010.