Tuto interactif: Test Unitaire avec Xcode

GreensourceGreensource Membre
juillet 2009 modifié dans Objective-C, Swift, C, C++ #1
prérequis: Connaà®tre un peu Xcode, avoir fait quelques petit projet avec.

Comme je suis parti sur ma lancé je vais faire un autre tuto tiens :P
Cette fois je m'attaque aux tests unitaires car il me semble que c'est une bonne façon de développer et que je ne le fait pas assez (du tout en fait  ::))

Bon d'abord il s'agit de savoir ce que c'est. Ce sont tout simplement des tests qui se charge vérifier les méthodes une à  une, vérifié que tel méthode fait bien ce qu'elle doit faire. J'ai suivit la page d'Apple à  ce propos pour me faire la main.

Alors, pour commencer il faut configurer Xcode:
  • Créer un bundle de test: Clic droit sur Target > Add > New Target. Ensuite sélectionner "Unit Test Bundle"
  • Configurer le bundle: Double clic dessus. Dans l'onglet général, ajouter une "Direct Dependencie", celle de votre appli.
    Passer à  l'onglet Build. Trouver les variables "Test Host" et "Bundle Loader". Il va faloir leurs dire quelle appli testé. Pour cela donner la valeur suivante: $(BUILT_PRODUCTS_DIR)/<lenomdevotreapp>.app/Contents/MacOS/<lenomdevotreapp> si vous avez fait une appli Cocoa.
    $(BUILT_PRODUCTS_DIR)/<lenomdevotreapp> si c'est un command tool.
    ça devrait indiquer <multiple value>, c'est normal.


Et pour finir, pour que votre projet execute bien les tests avant votre application, il faut changer la target du projet: Project > setActiveTarget > "votreBundleDeTest"

Voilà  votre projet est maintenant configuré pour faire des tests! Passons aux choses sérieuses...

ps: Le projet complet est en pièce jointe.

Réponses

  • GreensourceGreensource Membre
    04:27 modifié #2
    Créer son objet à  testé, uniquement les déclarations

    Ca peut paraà®tre étrange mais pour bien faire les choses on ne fait que déclarer les méthodes et ensuite directement on créer les jeux de test.
    En effet un test doit vérifier le résultat que l'ont attend de la part d'une méthode, et seulement ensuite on implémentera effectivement dans le but de coller au test. ça implique que le test soit bien conçu et le plus complet possible.

    Pour ce tuto j'ai choisit de simuler un compte en banque hyper simple. Quatre ivar: solde,plafond,decouvert et annulation. Une méthode d'init, une de dépot et enfin une de retrait.
    @interface Compte : NSObject {<br />	NSUInteger solde;<br />	NSUInteger plafond;<br />	NSUInteger decouvert;<br />	BOOL annulation;<br />}<br />@property (readonly) NSUInteger solde;<br />@property (readonly) NSUInteger plafond;<br />@property (readonly) NSUInteger decouvert;<br />@property (readonly) BOOL annulation;<br /><br />- (void)depot:(NSUInteger)valeur;<br />- (void)retrait:(NSUInteger)valeur;
    
  • GreensourceGreensource Membre
    04:27 modifié #3
    Créer les tests

    Il faut d'abord créer un objet de test. Pour cela, double clic sur le dossier Classes > Add > New File... là  dedans choisir "Objective-C test case class".
    Déclarer les méthodes de test que vous souhaiter. Ne pas oublier de testé l'initialisation!
    Puis les implémenter. Là  c'est assez simple, il faut juste connaitre les fonctions utiles. Pour chaque test vous vous faites un petit scénario avec votre objet et vous vérifiez qu'il réagit bien comme il faut. un exemple de ce que moi j'ai fait:
    - (void)testInitCompte<br />{<br />	Compte* unCompte = [[Compte alloc] init];<br />	STAssertNotNil(unCompte,@&quot;Initialisation ratée&quot;);<br />	STAssertTrue([unCompte solde] == kSoldeBienvenue,@&quot;Le solde n&#39;a pas la bonne valeur de départ&quot;);<br />	STAssertTrue([unCompte decouvert] == kDecouvertDefaut,@&quot;Le découvert n&#39;a pas la bonne valeur de départ&quot;);<br />	STAssertTrue([unCompte plafond] == kPlafondDefaut,@&quot;Le plafond n&#39;a pas la bonne valeure de départ&quot;);<br />	STAssertTrue([unCompte decouvert] &lt; [unCompte plafond],@&quot;La règle de la banque, découvert &lt; planfond, n&#39;est pas respecté&quot;);<br />}
    

    Bon par contre ça ne s'invente pas, pour connaà®tre les fonctions de test, j'ai suivit le lien d'Apple encore une fois.

    Et voilà ! Il ne reste qu'à  faire l'implémentation qui va bien! J'ai personnellement fait exprès dans le projet que j'ai mis en lien de générer tout un tas d'erreur pour bien illustrer l'intérêt des tests! A vous de faire une jolie implémentation et de posté ici une version qui fonctionne!

    Vous remarquerez que le grand intérêt d'inclure ça dans Xcode c'est que les erreurs apparaissent dans de zouli bubble rouge!  :P
  • AliGatorAliGator Membre, Modérateur
    juillet 2009 modifié #4
    1) Je ne crois pas que l'on soit obligé d'indiquer le chemin de l'exécutable à  tester : moi je ne l'ai jamais fait et il a toujours sû tester mon "target" final

    2) Ne pas oublier de préciser que les méthodes de test, pour qu'elles s'exécutent automatiquement avec SentestKit, doivent commencer par le mot "test". Notez qu'il n'est pas obligatoire pour ce cas d'avoir un ".h", le fichier ".m" pouvant être suffisant (d'autant que ce n'est pas nous qui appellons les fonctions de test, mais le script de SenTestKit/OCUnit qui scanne tes classes de TestU pour trouver tout seul les méthodes implémentées dans le .m)

    3) Rien n'oblige de créer les Tests U avant d'implémenter. Ca c'est si on fait du développement en TDD (Test-Driven Development). C'est vrai que le TDD est une bonne pratique car permet d'abord de s'imposer les contraintes de bon fonctionnement de l'appli avant de l'implémenter, au risque sinon d'écrire nos tests après "de sorte que ça passe". Mais ce n'est pas la seule façon de faire, ça dépend de la méthodologie adoptée.


    [EDIT] J'en profite pour mentionner ici les références à  l'astuce que j'avais développé pour afficher des messages lors de tests : ici une astuce pour permettre d'afficher des bubbles d'information (ou autre) lorsqu'un test est effectué, plus propre que de polluer les NSLogs et plus "intégré" à  Xcode puisque permet d'ajouter des bubbles qui apparaissent dans le code source.
  • GreensourceGreensource Membre
    juillet 2009 modifié #5
    Merci de ces précisions Ali! ;)
    Pour ce qui est de la méthode TDD, certes elle n'est pas obligatoire mais je sais que quand je fait sans, mes tests sont un peu n'importe comment. Fait de sorte que ça passe comme tu dis.

    Pour ce qui est des messages arrivant sous forme de bubbles il y a du avoir une mise à  jour car moi ça l'a fait tout seul.

    Et pour ce qui est du chemin de l'exécutable, j'ai essayer sans, ça ne marche pas. On a pas dû faire exactement les mêmes manip je penses.
  • AliGatorAliGator Membre, Modérateur
    04:27 modifié #6
    Non non y'a pas eu de mises à  jour ça les met tout seul : si tu as des tests qui ne passent pas, ça te les met en Bubbles quand tu utilises STAssertXXX.

    Mais dans mon cas je souhaitais afficher de toute façon un message, que le test passe ou pas. Par exemple :
    - Afficher un message "Starting test XXX" au début de chaque test
    - ou alors quand je fais des tests aléatoires (stress tests qui modifient par exemple une valeur de variable d'instance un nombre N de fois compris entre 50 et 70, en faisant des pauses de P millisecondes entre chaque modification, P entre 1 et 20, histoire de pas avoir un test trop déterministe), afficher les tests qui sont faits (le nombre de fois que j'ai "stressé" ma variable par exemple), ...
    - ou encore quand il y a un test qui ne peut être vérifié par code : par exemple récupération d'un fichier XML se trouvant sur un serveur puis traitement de ce XML pour en extraire une valeur... et je dois m'assurer que cette valeur correspond à  ce que je vois disons en bas à  gauche quand je charge une certaine page dans Safari... Bah dans ce cas je fais le test automatique de récup du XML dans OCUnit, et je veux afficher qqpart la valeur retournée par cette récupération pour me permettre une fois le test terminé de vérifier à  la main si la valeur obtenue est la bonne
    ...


    Pour tous ces Use Cases là , on peut donc avoir besoin d'afficher un message (une "bubble") dans Xcode, mais qui n'est pas lié à  une assertion, et qui n'a pas de conséquence sur la réussite ou non du test, mais permet juste d'afficher un message informatif. Et c'est là  que ma macro STMessage m'a bcp servi ;)
  • GreensourceGreensource Membre
    04:27 modifié #7
    Je vais compléter un peu ce tuto car pour l'instant vous ne pouvez pas vous servir du debugger pour tracker les erreurs. Or c'est quasi indispensable dans la réalitée. Il y a quelques manip à  faire mais elle sont un peu relou.
    1°) Double cliquez sur votre projet (juste en dessous de "Groups & Files" en haut à  gauche). Sélectionné l'onglet "General" et passer la valeur de "Places Intermediate Build Files In:" à  "Build product location".

    2°) Double cliquez sur votre exécutable (Toujours dans le "Groups & Files"). Choisissez l'onglet Argument. Là  il faut ajouter l'argument:
    -SenTest All


    3°) Toujours dans cet onglet, y a 4 autres trucs relou à  ajouter. Mais c'est un peu galère à  écrire ici donc je vous met en lien la page d'Apple: Using the Debugger with OCUnit
    C'est vers la fin. Bon faut pas prendre leur truc au pieds de la lettre, vous devez adapté les deux dernière variable XCInjectBundle et XCInjectBundleInto avec le nom de votre projet. Ce qui donne pour cet exemple:
    image2uhd.png


    4°) Et pour finir il y a un petit fichier à  modifier pour le debugger. Tapez simplement dans le Terminal de MacOSX:
    cd ~
    echo '' >> .gdbinit
    echo 'set start-with-shell 0' >> .gdbinit

    Et voilà  c'est prêt! Notez que la 4ème étape n'est à  faire qu'une seule fois tandis que les autres sont à  faire pour tout vos projets.
    Notez aussi que vous ne pouvez pas utiliser Build & Go! Il faut d'abord faire Build et ensuite Debug.
Connectez-vous ou Inscrivez-vous pour répondre.