Tests unitaires et tests d’interface sur iPhone : État des lieux
Dans un précédent article sur la mise en place d’une Usine de dev iPhone nous avions promis un article sur la pratique des tests unitaires sur iPhone, le voici !
Venant de l’univers JAVA où ce sujet est très présent, nous avons voulu reproduire sur nos projets iPhone les bonnes pratiques de tests automatisés auxquelles nous étions habitués.
Néanmoins la situation est bien différente sur une application mobile et sur iPhone en particulier : l'interface graphique est au cœur de l’application, et celle-ci semble particulièrement complexe à tester dans le cas d'un mobile.
De plus les outils et bonnes pratiques largement répandus en Java et .NET sont encore méconnus dans la communauté Objective-C : cet article est pour nous l'occasion de partager nos découvertes sur ce sujet.
Alors, qu’est-il possible de faire en matière de tests unitaires et de tests d’interfaces sur iPhone ?
Tests unitaires
Nous avons commencé par mettre en place des tests unitaires car ils sont plus rentables à court terme que les tests d’interface: en effet c’est une approche connue, plus facilement automatisable et qui apporte des bénéfices immédiats, en particulier la possibilité de développer en TDD.
Il existe 2 principaux frameworks de tests unitaires pour iPhone :
- “OCUnit“ qui est l’outil officiellement supporté par Apple
- “Google Toolbox for Mac” qui est un projet Google
OCUnit :
OCUnit est un framework de test unitaire intégré dans l’environnement de développement iPhone depuis le SDK 2.2
Pour créer une suite de tests il suffit de créer une target de type “Unit Test Bundle”. On exécute ensuite les tests en lançant cette target.
Les tests qui échouent apparaissent dans le code comme des erreurs de compilation :
OCUnit est un projet stable et mature mais qui manque de souplesse. En effet ce framework possède de nombreuses limitations :
- Des logs d’erreurs peu explicites
- Certains tests doivent être lancés sur le device et ne sont donc pas automatisable (impossible de les intégrer à un processus d'intégration continue)
- Il faut beaucoup de configuration pour debugger les tests
- Il est compliqué d’accéder aux ressources du projet
Pour en savoir plus :
Apple propose un tutorial sur la mise en place d’OCUnit sur un projet iPhone.
Le blog CocoaWithLove propose un article très complet sur les tests unitaires avec OCUnit.
Google Toolbox for Mac (GTM) :
Google Toolbox for Mac est un projet open source de Google qui offre des utilitaires de développement pour Mac. Il propose notamment un framework de test unitaire pour iPhone qui permet de régler la plupart des problèmes rencontrés avec OCUnit :
- On peut accéder aux composants graphiques (zone de saisie, table, etc)
- Il est possible de lancer tout les tests sur le simulateur
- On peut lancer les tests en debug sans configuration supplémentaire
- On accède sans difficultés aux ressources du main bundle
- Il offre des fonctions de tests d'interface (nous en reparlerons)
GTM est un framework moins mature qu’OCUnit, mais bien intégré dans Xcode. Il ne s’agit pas d’un projet soutenu par Apple on s’expose donc à des possibles régressions à chaque nouvelle version d’Xcode.
C’est néanmoins l’outil que nous avons retenu : sa mise en place est simple et il est beaucoup moins contraignant à l’utilisation qu’OCUnit. On apprécie également le support offert via le groupe de discussion du projet.
On trouve ici un tutorial complet sur la mise en place de GTM ainsi qu’un exemple.
Il existe également un plugin Xcode pour GTM.
Les Mocks
Pour écrire des tests unitaires automatisés qui sont répétables et autonomes, on a souvent besoin de "mocks" (plus communément appelés bouchons : il s'agit de créer un objet qui simulera un composant dont on souhaite s'abstraire dans nos tests : une base de donnée, un webservice, etc.). Sur Java il existe des librairies pour nous aider (EasyMock, Mockito), mais il en existe aussi pour le développement iPhone.
OCMock :
OCMock est un framework permettant facilement de mocker une classe. Pour en savoir plus sur le concept de mock : http://en.wikipedia.org/wiki/Mock_object
Il permet notamment :
- De bouchonner des méthodes
- De forwarder un appel de méthode vers une méthode de son choix
- De vérifier l'enchaînement des appels reçus par une classe
L'utilisation d'OCMock reste la meilleure approche pour tester des appels à des composants dont on ne maitrise pas le comportement : GPS, appareil photo, base de données, webservice, etc.
A noter que son utilisation n’est pas intuitive si on n’est pas déjà habitué à la manipulation d’un framework de Mock et il est peu documenté.
Voici un lien vers un tutorial sur la mise en place d’OCMock.
Tests d'interfaces
En ce qui concerne les tests d’interfaces il y a 2 types de tests que l’on souhaitait effectuer : d’une part s’assurer que chaque page a bien le look voulu, d’autre part que les actions faites par l’utilisateur provoquent bien le résultat attendu.
La bonne nouvelle est qu’il existe des outils pour faire ces 2 types de tests !
Tester en pilotant l’application : UISpec
UISpec est un framework open source de Behavior Driven Development (BDD). Derrière cette appellation barbare on trouve un outil qui permet de piloter le simulateur (ou l’iPhone) par le code et simuler les actions de l’utilisateur : faire un touch sur un bouton, la cellule d’un tableau, etc.
Il permet surtout de faire des assertions sur l’interface pour vérifier que tel label contient la string attendue, que tel tableau contient le bon nombre de cellule, etc.
C’est donc un outil extrêmement puissant pour tester le comportement de fonctionnalités complexes. Voici une vidéo d’une suite de tests sur la recherche de réparateurs auto que nous avons mis en place sur l’application Generali Responsabilité Accident Auto
N.B : A droite on trouve un fichier de log avec le résultat des assertions, nous avons fait clignoter les éléments graphiques qui sont testés.
La syntaxe utilisée par UISpec est assez simple et très lisible, on arrive rapidement à tester des fonctionnalités simples.
Exemple:
[[app.textField.with placeholder:@"First Name"] setText:@"Brian"];
Néanmoins on se rend compte à l’usage qu’il existe de nombreux cas particulier qui se révèlent complexe à tester : il y a un véritable investissement à faire pour trouver les “astuces” adéquates. La maintenance de ces tests est également très couteuse :
- Il est complexe de rendre les tests indépendants les uns des autres : à chaque début de test il faut naviguer dans l’application pour se rendre à la page que l’on veut tester. Et à la fin de chaque test il faut s’assurer de rétablir l’application dans son état d’origine.
- Il est compliqué d’accéder de façon fiable à des composants qui ne possèdent pas de libellé (ex: pour faire un touch sur la loupe en haut a gauche on doit écrire : “fait un touch sur le 5eme bouton de la page”).
Dernier point problématique l’exécution d’une suite de tests complète est longue et ceux ci ne sont pas automatisable. Il n’est pas encore possible d’intégrer ces tests à un processus d’intégration continue.
Tester le look d’une page
GTM propose 2 solutions pour tester l’apparence d’une vue :
- On compare qu’une vue est identique à une image de référence
- On compare que toutes les propriétés d’une vue (transparence, position, subviews, etc.) sont identique à un XML de référence
On trouve un exemple complet de test par comparaison d'images ici.
Malheureusement ces fonctionnalités sont assez mal documentées, les infos se trouvent dispersées sur le groupe de discussion de GTM.
Notre bilan est assez mitigé concernant ce type de tests. S’ils sont simples à écrire (3 lignes de codes par écran), et simples à maintenir, leur intérêt est quasi nul pour des écrans classiques. En effet on ne peut tester que le look initial d’une page or celui ci est rarement sujet à régression. On aurait voulu tester le look de la page après des actions de l’utilisateur (vérifier que tel composant est bien apparu par exemple).
Ils n’apportent de valeur que dans le cas du test de fonction de dessin (l’affichage de graph par exemple). En effet on dispose de fonctions pour vérifier que le résultat d’une méthode de dessin est bien équivalent à une image de référence.
III) Tableaux récapitulatif
A l'usage voilà notre bilan sur ces différents outils :
Coût d’écriture | Coût de maintenance | Temps de prise en main | Bénéfice retiré | |
Test unitaire avec OCUnit | ++ | ++ | + | ++ |
Test unitaire avec GTM | ++ | ++ | + | ++++ |
Bouchons avec OCMock | ++ | + | +++ | ++++ |
Test d’interface avec UISpec | +++ | +++ | ++++ | +++ |
Test d’interface avec GTM | + | + | + | + |
Pour conclure nous utilisons aujourd'hui :
- Google Tool Box pour les tests unitaires
- OCMock pour créer des bouchons
- UISpec pour les tests d'interface