Chez XPEHO on aime la mobilité et on aime aussi les applications de qualité.
Beaucoup de développeurs vous le diront. : Tester c’est douter. Oui, mais dans le doute, il vaut mieux tester !
Les tests automatisés, qu’est-ce que c’est ?
Les tests automatisés font partie intégrante d’un développement. Il est très confortable de pouvoir exécuter une suite de tests pour s’assurer de la non-régression après avoir effectué une modification dans le code. C’est une bonne pratique qui permet de créer et de maintenir des applications de meilleure qualité. Même si les tests automatisés ne remplaceront jamais les tests effectués par un être humain, ils permettent de vérifier très rapidement l’indice de confiance et de qualité de ce qu’on s’apprête à livrer.
Les différents tests avec Flutter
Les tests unitaires
C’est le plus bas niveau de granularité. Ce sont des tests très petits qui permettent de tester une seule méthode. Par convention, il est préférable d’utiliser des mocks pour tout appel vers l’extérieur de la méthode testée. Par exemple : si la méthode testée fait appel à une API, on remplacera l’appel API par un Mock.
Les tests widget
On teste ici chaque widget de manière indépendant. Ces tests permettent de valider le comportement d’un widget personnalisé en fonction du cycle de vie et des actions effectuées.
On peut ainsi tester le comportement et les changements graphiques de notre widget en fonction des actions effectuées dessus.
Les tests Golden
C’est une variante des tests widget. On teste ici le rendu graphique d’une vue. Ces tests permettent de générer une image de ce à quoi est censé ressembler le widget en fonction de différentes conditions (résolution, écran téléphone ou tablette, etc…). Lors que ces tests s’exécutent, ils vont comparer une image de référence au rendu du widget testé. On peut ainsi trouver facilement les impacts potentiels sur toutes les vues de notre application suite à un changement de thème ou de police.
Je vous conseille cet article qui parle des tests widget de façon plus détaillée.
Les tests d’intégration
Les Integration Tests de Flutter sont ce qui se rapprochent le plus des tests utilisateurs. Ils permettent de tester les différents parcours de l’application, la navigation et les gestes métier. Tous comme les autres tests, ils permettent de bouchonner les appels API en les remplaçant par des Mocks. De cette manière, vous serrez capable de valider la totalité des parcours graphique de votre application et en même temps de maîtriser vos données.
Vous pouvez bien entendu laisser vos API telles qu’elles sont, mais vous serez alors dépendant de données sur lesquelles vous n’avez aucun contrôle et votre test risquera d’échouer en fonction du contenu de ces données.
Mocks, qu’est-ce que c’est ?
En anglais, to mock peut se traduire par « singer » ou « imiter ». C’est une pratique qui consiste à remplacer le comportement d’un morceau de code par un faux comportement dans le but de contrôler le bon déroulement de nos tests.
Par exemple, si vous cherchez à tester le bout de code qui interprète la réponse d’un appel API, vous allez remplacer le bout de code qui appel l’API par un mock.
Dans votre premier test, vous allez vérifier le comportement quand tout se passe bien et dire à votre mock de renvoyer une réponse success. Enfin, dans votre second test, vous allez vérifier le comportement lorsque l’API vous renvoie une erreur et dire à votre mock de renvoyer une réponse error.
La plus répandue des librairies de mocks Java est Mockito pour Mock It To. Flutter propose sa version de Mockito pour Dart. Et si vous faites du Kotlin, je vous recommande Mockk pour faire vos mocks.
Les tests end to end
C’est quoi ?
Traduisez tests de bout en bout. Ces tests vont permettre de tester l’intégralité des briques qui permettent le bon fonctionnement d’une application. Une application mobile n’est pas toujours autonome, elle utilise parfois (voire souvent) des API HTTP, des bases de données, d’autres applications, les capteurs du téléphone etc…
A quoi ça sert ?
Les tests end to end permettent de tester automatiquement les scénarii fonctionnels d’une application. On y déroule une série de gestes ou d’actions que l’utilisateur est censé effectuer dans le but de vérifier le bon fonctionnement de l’application.
Cette catégorie de test nécessite plus d’efforts pour les mettre en place. Il est donc important de se limiter aux 20/80 (20% des cas qui représentent 80% de la valeur de l’application). Entendez par là qu’il faut se limiter aux cas nominaux et éviter de tester l’intégralité des cas possibles ou des erreurs potentielles.
Pourquoi en faire ?
Cette catégorie de tests est très adaptée pour vérifier rapidement si la dernière version de l’application contient ou non des régressions. Elle permet d’obtenir un indice de confiance sur la qualité de la version actuelle de l’application.
Difficultés rencontrées
L’une des particularités de Flutter est le moteur de rendu Skia https://skia.org/. Ce moteur permet d’afficher le contenu de l’interface utilisateur d’une application Flutter. En gros, Flutter affiche une vue native dans laquelle il dessine ses propres pixels.
Les outils existants qui permettent de faire des tests end to end fonctionnent tous de la même manière :
- Localiser une vue ou un élément sur l’écran d’une application
- effectuer une action sur cet élément (clic, assertion…)
Comme Flutter ne dispose que d’une seule vue, il est donc impossible de procéder de la même manière puisque les différents éléments de l’interface ne sont pas accessibles depuis l’extérieur de l’application.
De ce fait, les outils traditionnels de tests end to end ne peuvent pas tester une application Flutter.
Comment en faire alors ?
Il suffit de trouver un moyen de mettre en place un robot de test qui procède de la même manière qu’un être humain. Lorsque nous utilisons une application, nous utilisons 3 outils :
- Nos yeux
- Notre cerveau
- Nos mains
Un robot qui devrait faire de même doit donc les inclure :
- La reconnaissance d’images ou de texte ferait office d’œil
- Le scénario de test remplacerait le cerveau
- Un robot de test comme Appium remplacerait les mains
RobotFramework
RobotFramework c’est quoi ?
C’est un outil d’automatisation de tests qui sait à peu près tout faire. IHM, API, serveurs, BDD, desktop, web, mobile ou un peu de tout ça en même temps, il sait le faire sans souci.
Plus d’infos sur https://robotframework.org/
Comment ça fonctionne ?
C’est basé sur Python. Le principe de fonctionnement est assez simple.
On crée des fichiers *.robot dans lesquels on écrit des tests dans la langue souhaitée. Anglais, français, espéranto, c’est vous qui choisissez.
Par exemple
*** Settings ***
Resource welovedevs_keywords.resource
*** Test Cases ***
Read previous post
Open Google Chrome
Read post https://welovedevs.com/fr/articles/cicd-flutter-avec-github-actions-par-xpeho/
Ensuite, vous devez définir ce que « Open Google Chrome » et « Read post » vont faire dans le fichier « welovedevs_keywords.resource »
*** Settings ***
Library Browser
*** Keywords ***
Open Google Chrome
New Browser chromium headless=false slowMo=0:00:00.050
New Context viewport={'width': 1920, 'height': 1080}
Read article
[Arguments] ${url}
New Page ${url}
Get Title == Google
On utilise ici une librairie « browser » qui permet de manipuler le navigateur web de votre ordinateur.
Plus d’infos sur les librairies de RobotFramework sur https://robotframework.org/#resources
Comment l’utiliser pour Flutter ?
Sur nos trois outils, les yeux, le cerveau, les mains, il ne nous manque plus que les yeux. Si vous avez bien suivi, Appium fera les clics sur le téléphone et RobotFramework contiendra la partie intelligente, à savoir les scénarii de tests. Et bien bonne nouvelle, RobotFramework a des librairies capables de donner la vue à nos tests !
On a besoin de savoir reconnaître 2 choses :
- du texte
- des images
Pour le texte, il nous faut une librairie d’OCR. Il s’agit de reconnaitre du texte à l’intérieur d’une image. Et on fera ça avec OCRLibrary https://pypi.org/project/robotframework-ocrlibrary/
Pour les images, il nous faut ce qui s’appelle de l’image template. C’est le fait de trouver une image à l’intérieur d’une image plus grande. Et tout ça grâce à OpenCV de Python https://pyimagesearch.com/2021/03/22/opencv-template-matching-cv2-matchtemplate/
Un exemple
On a créé une petite application en Flutter avec des scénarii de tests RobotFramework
L’application de base générée par Flutter est plutôt simple. Nous avons donc ajouté quelques éléments pour avoir de quoi tester.
Avant de pouvoir tester quoi que ce soit, il faut construire l’application et l’installer sur l’émulateur Android.
Dans un terminal à la racine du projet tapez
flutter build apk --debug
et
flutter install
Maintenant écrivons un premier test dans un fichier counter_app.robot
pour vérifier qu’on arrive à incrémenter le compteur
*** Settings ***
Documentation Counter app test suite
Resource counter_app_keywords.resource
Test Setup Open the counter app
Test Teardown Close the counter app
*** Test Cases ***
Counter app increment counter
Given I have opened the counter app
When I click on the increment button 6
Then the counter should incremented by 6
Définissons les mots clés dans le fichier counter_app_keywords.resource
*** Settings ***
Documentation TODO
Library AppiumLibrary
Library OCRLibrary
Library Collections
Library custom_lib.py
*** Variables ***
${APP} com.example.flutter_tests
${IMAGE_DIR} ${CURDIR}/images
${ANDROID_AUTOMATION_NAME} UIAutomator2
${IOS_PLATFORM_NAME} iOS
${ANDROID_PLATFORM_NAME} Android
*** Keywords ***
Open The counter app
Open Application http://localhost:4723/wd/hub automationName=${ANDROID_AUTOMATION_NAME}
...platformName=${ANDROID_PLATFORM_NAME}
... app=${CURDIR}/../../build/app/outputs/flutter-apk/app-debug.apk
Close The counter app
Close Application
Given I have opened the counter app
Find Text You have pushed the button this many times:
Find Text 0
When I click on the increment button
[Arguments]${clickCount}
Click Image plus_button.png ${clickCount}
Then the counter should incremented by
[Arguments]${clickCount}
Find Text ${clickCount}
import cv2
import numpy as np
def assert_text_was_found(coordinatesList, text):
if coordinatesList is None:
print('Text ' + text + ' was found')
raise Exception("Text '{}' was not found".format(text))
elif len(coordinatesList) == 0:
print('Text ' + text + ' was not found')
raise Exception("Text '{}' was not found".format(text))
else:
print('Text ' + text + ' was found')
def find_sub_image(im1, im2):
needle = cv2.imread(im1)
haystack = cv2.imread(im2)
result = cv2.matchTemplate(needle, haystack, cv2.TM_CCOEFF_NORMED)
y, x = np.unravel_index(result.argmax(), result.shape)
x += (needle.shape[1]/2)
y += (needle.shape[0]/2)
return x, y
robot counter_app.robot
Des cas plus complexes ainsi que la totalité des sources sont disponibles sur notre repo Git.
Conclusion
Nous venons de créer un outil qui permet de faire des tests End To End facilement sur des applications Flutter mobiles. Cet outil est capable de chercher, trouver et interagir avec des éléments dans l’interface de notre application. Ces éléments peuvent être des images ou du texte.
Si un élément change de position dans l’écran, notre outil est capable de le retrouver sans faire de modification à nos tests. Il est possible de faire fonctionner ces tests sur Flutter Web ou même sur Flutter Desktop avec un jeu de librairie RobotFramework différente.
Le mot de la fin
Les tests sont une partie souvent trop négligée dans notre métier. Pourtant, ils permettent avec très peu d’investissement de s’assurer de la qualité de notre application.
Flutter est très bien outillé pour faire des tests et l’outil qu’on vient de créer permet de tester nos applications à un niveau supplémentaire alors pourquoi se priver ?