În acest articol se va face o trecere în revistă a elementelor cheie specifice MAF - API dezvoltat intern în Telenav pentru testare automată folosind Appium. Acesta din urmă oferă posibilitatea rulării aceluiași set de teste simultan pe mai multe dispozitive iOS, respectiv Android. Se vor analiza câteva aspecte generice legate de serviciul de automatizare Appium și se va realiza un tutorial despre instalarea și configurarea API-ului.
Appium este un framework open-source destinat automatizării diferitelor scenarii de testare pentru aplicații mobile. Platformele pe care rulează în prezent sunt iOS, Android și Windows, având la bază protocolul WebDriver. Este, de asemenea, cross-platform, ceea în contextul de automatizări înseamnă permisiunea de a scrie testele pentru platformele menționate anterior având la bază același API. Astfel se permite reutilizarea codului, atâta timp cât interfața de utilizare a aplicațiilor este similară. Toate aceste avantaje duc la o arhitectură ușor de structurat pentru propriul framework. Un alt avantaj este flexibilitatea în alegerea limbajului de programare pentru implementarea testelor. În prezent, utilizatorul poate opta pentru folosirea Java, Ruby, Python, PHP, JavaScript sau C #.
O problemă de actualitate este lipsa unei funcționalități open-source care să susțină execuția mai multor sesiuni simultan, ceea ce conduce la imposibilitatea rulării aceluiași set de teste pe multiple dispozitive în același timp.
Profitând de avantajul funcționalității cross-platform, a fost dezvoltat un nivel de funcționalitate peste Appium având ca scop rularea simultană de testare pe diferite dispozitive fizice.
API-ul are la bază o arhitectura abstractă, conținând o clasă Client
compusă din alte trei: TouchGestures
, ElementValidation
și ElementLogging
, unde fiecare clasă responsabilă de interacțiuni se conectează la clasa Driver
al cărei scop principal este furnizarea logicii de conectare cu dispozitivul mobil.
Pentru implementarea acestei soluții trebuie ținut cont de următoarele aspecte:
Clasa Driver
- un manager responsabil de manipularea diferitelor conexiuni dintre dispozitive și clienți;
Utilități generice specifice oricărui proiect de automatizare:
Gestures
- clasă responsabilă de gestionarea diferitelor tipuri de interacțiuni cu dispozitivele mobile: tap pe un element de UI din cadrul ecranului, dublu tap, swipe, pinch;
Logger
- responsabilă de obținerea informațiilor specific elementelor de UI precum: id-ul/accessibility identifier-ul unui buton, logarea ierarhiei elementelor de UI sau locația unui buton pe ecranul unui dispozitiv;
Validator
- responsabil cu verificarea vizibilității precum și a altor stări a elementelor de UI;Client
- o componentă prototip ce stă la baza fiecărui caz nou de testare pentru realizarea interacțiunii cu dispozitivul la care clientul se conectează.În cadrul acestei secțiuni se descriu câteva aspecte despre instalarea și configurarea API-ului, principalele acțiuni oferite de MAF API, urmată de o scurtă prezentare a modalității de implementare a testelor. Versiunea curentă susține automatizări pe două platforme (Android respective iOS) și are la baza Mac OS.
Setupul inițial - următoarele dependințe sunt necesare: Appium Version: 1.6.3, MacOS: el capitan, iPhone OS >= 10.0, Python 3, Xcode8
Instalare Appium - un ghid complet poate fi găsit la adresa următoare https://appium.io/getting-started.html?lang=en.
Dependințe:
carthage - manager pentru descentralizarea dependințelor de frameworkul Cocoa;
libimobiledevice - protocol cross-platform folosit pentru conexiunea cu dispozitivele iOS;
ios-deploy - aplicație pentru instalarea de aplicații iOS pe dispozitive fizice
carthage: brew install carthage
brew uninstall libimobiledevice && brew install --HEAD libimobiledevice
brew install ios-deploy
Cerințe specifice iOS - Utilitarul XCode WebDriverAgent trebuie configurat în concordanță cu un provisioning profile valid. Începând cu versiunea de iOS 10.0 acest tool acționează ca o legătură dintre teste și aplicație.
Odată terminată configurarea, urmează implementarea testelor. În mod obișnuit pentru a folosi serviciul Appium, primul lucru ce trebuie făcut este pornirea serverului printr-un UI extern. Pentru a evita acest lucru s-a implementat un API responsabil de manipularea acestor acțiuni.
appium_instance_ios = AppiumServer()
appium_instance_android = AppiumServer()
appium_instance_ios.start(port=9000) appium-python-client
appium_instance_android.start(port=8000)
Această bucată de cod este exemplul perfect de expunere a modului de rulare a aceluiași test pe două dispozitive diferite. Clasa AppiumServer reprezintă implementarea în Python a serverului Appium. În continuare, se creează o instanță de iOS respectiv Android, iar acestea apelează aceeași metodă start(). Punctul cheie în rularea simultană a testului pe două dispozitive este reprezentat de parametrul port. În acest caz pentru iOS se folosește portul:9000 iar pentru Android:8000.
Clasa Driver
se comportă ca un factory fiind folosit în implementarea clasei Client în vederea setării conexiunii cu instanța serverului Appium
dorită. Trebuie, de asemenea, avut în vedere o relație de unu la unu dintre instanța clientului și server. Clasa Driver
folosește librăria de appium-python-client , aceasta furnizând o instanță a web-driver-ului în vederea transmiterii de comenzi către serviciul de Appium. În primul rând, se face un apel către Client
pentru o instanță de web-driver, clasa de Driver
creează o conexiune și mapează instanța la un identificator unic al clientului. În acest fel, clientul cere o nouă conexiune, iar clasa Driver
furnizează instanța deja mapată. Pe scurt, clasa Driver
administrează o conexiune de tip singleton la serviciul de Appium.
Fiecare clasă client conține implementarea celor trei clase de utilitare abstracte: Gestures
, Logger
și Validator
corespondente driverului specific, în acest caz appium-python-client
driver. Pe lângă metodele abstracte obligatorii, clasele pot fi extinse cu alte funcții specifice clientului : de exemplu, dacă text-value al unui element specific este necesar atunci o metodă numită get_text_from
va fi definită în implementarea clasei Logger atât timp cât ea manipulează informații cu privire la elementele de UI. Dacă este necesară validarea unui atribut de text al unui element, o metodă "validate_text" va fi creată în implementarea clasei Validator
având în vedere că metoda returnează o valoare True
sau False
.
Decoratorul @test_case("test_ID")
creează o legătură dinamică între fișierul de date al testelor și implementarea acestora folosind un identificator test_ID
. Păstrând datele de test într-un fișier separat și structurat pe baza referințelor cu identificatorii unici ai testelor, se oferă o mentenanță simplistă pe termen lung. Datele conțin atât valori de input cât și date de referință pentru a putea valida/invalida un test. Ele sunt transmise printr-un argument obligatoriu "kwargs" al metodei - o structură de date de tip dicționar. Accesul la aceste date se oferă prin cheia 'test_data' din dicționarul de unde este chemat. Mai mult decât atât un mecanism de tratare a erorilor este responsabil pentru furnizarea unui flow de salvare a logurilor în caz de crash al aplicației.
Ținând cont de tot ce s-a discutat anterior, în continuare este prezentat un exemplu care efectuează câteva interacțiuni cu ecranul dispozitivului mobil.
@test_case("TEST_ID_01")
def my_first_test(**kwargs):
# kwargs['test_data'] holds all the test specific data
text_input = kwargs['test data']['input_text']
try:
# test case steps for android client
appium_service_android = AppiumServer()
appium_service_android.start(port=8000)
android_client = AndroidClient()
# test case steps for iOS client
appium_service_iOS = AppiumServer()
appium_service_iOS.start(port=9000)
iOS_client = iOSClient()
android_client.gestures.tap('input_box')
android_client.gestures.send_data_to(
'input_box', text_input)
android_client.gestures.tap('send_button')
# the data is send to the iOS application
# check the iOS app if received the data.
request_message = iOS_client.logger
.get_text_from('request_message')
# validate the step
assert iOS_client.validator
.check_value('request_text', text_input)
print("Test case ended with status: OK")
except Exception as ex:
print("Error executing test case: "
+ str(ex), file=sys.stderr)
finally:
appium_service_android.close()
appium_service_iOS.close()
Multiplele ore de lucru și cercetare a serviciului Appium au dovedit că acesta este un framework stabil pentru proiectele de testare automată. Mai mult decât atât, flexibilitatea de a alege limbajul de programare preferat cât și operabilitatea între platforme poate transforma procesul de 'continuous delivery' într-o treabă ușoară pentru orice programator. În contextul Telenav, odată implementat, API-ul respectiv a facilitat enorm efortul de testare manuală și mai mult decât atât, a creat o soluție eficientă pentru crearea de noi cazuri de testare pentru orice utilizator.
de Bálint Ákos
de Andrei Oneț
de Raul Boldea
de Ioana Varga