Android UI ტესტირება და Legacy კოდი

Image for post
Image for post
Photo by Pathum Danthanarayana on Unsplash

როდესაც საუბარი იწყება ტესტირებაზე Android პლატფორმის შიგნით ბევრი ჩუმდება. ყველას გვახსენდება ჩვენი შავბნელი ViewModel-ები და WorkManager-ები. რაღაც მომენტში გული მშვიდად გვაქ, რადგან ვიცით რომ activity/fragment-ში ჩადებული ცოდვები unit და instrumental ტესტებს არ ეხება. მაგრამ ფრაგმენტებში ისედაც არ უნდა იყოს არაფერი ლოგიკა გასატეტესტი, არა? 😈

აი როდესაც საუბარი იწყება ავტომატურ UI ტესტირებაზე Android-ში უფრო მეტი დუმდება. მოგონებებში ჩნდება არასტაბილური ტესტები , უამრავი ხელსაწყო და დოკუმენტაცია რომელიც გვპირდება დახმარებას მაგრამ რეალობაში გამოყენება ბევრად რთული აღმოჩნდება ხოლმე. პატარა საჩვენებელ მაგალითებში რომელიც Github-ზე ბევრი ვარსკვლავით იწონებენ თავს რეალურ ცხოვრებაში უსურად ჩანან. გასათვალისწინებელია რომ ძალიან ბევრი აპლიკაცია იქამდე დაიწერა სანამ Google და ზოგადად Android გუნდი აქტიურად დაიწყებდნენ კარგი კოდის წერის და აპლიკაციის არქიტექტურის პრაქტიკის გაზიარებას, შესაბამისად დღეს არსებული ხელსაწყოები ისე მარტივად ვერ ჭრის პრობლემას რომელიც მის გამოსვლამდე ბევრად ადრე შეიქმნა.

ხელსაწყოები

მოდი ვისაუბროთ რა ხელსაწყოები გვაქვს ინვენტარში. რა დაგროვდა დროთა განმავლობაში.

FragmentScenario

იზოლირებულად ფრაგმენტის გასატესტად გასულ წელს გამოვიდა შესანიშნავი api - FragmentScenario, რომელსაც შეუძლია ფრაგმენტის როგორც გრაფიკული ასევე არაგრაფიკული ტესტირება. გრაფიკული ფრაგმენტის შემთხვევაში UI ელემენტებთან სამუშაოდ შემდგომ შეგვიძლია Espresso-ს api დავიხმაროთ. აქვს FragmentFactory-ს მხარდაჭერა და გვაძლევს საშუალებას ჩვენი ფრაგმენტი სხვადასხვა state-ში გადავიყვანოთ სატესტოდ. ტესტში ვქმნით ჩვენთვის მოსაწონ სცენარს და ვმანიპულირებთ ფრაგმენტზე. დოკუმენტაციაც საკმაოდ ნათლად არის დაწერილი.

ActivityScenario

იგივე ეხება ექთივითების გასატესტ ხელსაწყოსაც ActivityScenario. შეგვიძლია სხვადასხვა სცენარის შექმნა და state-ების მანიპულაცია. დანარჩენი დეტალები შეგიძლიათ ნახოთ ოფიციალურ დოკუმენტაციაში.

Espresso UI testing

ჩვენი ძველი მეგობარი, ყოვლისშემძლე Espresso. წლებია ჩვენს გვერდით არის, მრავალფეროვანი api-ს და სხვა დამატებების წყალობით შეუცვლელი ხელსაწყოა. გამოიყენება ჩვენი აპლიკაციის შიგნით ui ელემენტებზე მანიპულაციისთვის, მაგრამ არამარტო ui, intent, webview და სხვა კომპონენტებთან სამუშაოდ.

UIAutomator

კიდევ ერთი დინოზავრი, რომელიც უკვე ზოგადად Android სისტემაში ui ავტომატიზაციისთვის და იმის შესამოწმებლად ჩვენი აპლიკაცია როგორ იყენებს სხვა აპლიკაციებს ინფორმაციის მისაღებად და ინტეგრაციისთვის. UIAutomator-ს გამოყენება შესაძლებელია ასევე Appium-ში.

მხეცის მოთვინიერება

ახლა მოდი ვისაუბროთ თითოეულის გამოყენების შესაძლებლობაზე განსაკუთრებით legacy კოდის გარემოში.

რატომ არ გვაწყობს სცენარების დაგეგმვა? ორივე მათგანი მუშაობს სრულად იზოლირებულ გარემოში. ფრაგმენტის შემთხვევაში კოდი რომელიც activity-დან ითხოვს ობიექტებს ვერ იმუშავებს სცენარის პირობებში რადგან არ იქნება მიბმული ჩვენს კონკრეტულ ექთივითის შიგნით. თუ მხოლოდ activity context გვჭირდება შესაძლებელია ცარიელ კონტეინერ ექთივითიში გაშვება launchFragmentInContainer ამ მეთოდით, თუმცა როგორც აღვნიშნე ისეთ ფრაგმენტებში რომელიც კონკრეტულ ექთივითიზე არიან დამოკიდებული ვერ გავტესტავთ. ამასთანავე ფრაგმენტი სხვადასხვა ლეიაუთ კონფიგურაციაზე ერთნაირი უნდა იყოს, ვგულისხმობ როგორც დიდი განზომილების ეკრანისთვის ასევე ტაბლეტებისთვის მორგებულ ვიზუალს.

იგივე ეხება activity სცენარების ტესტირებას. მასაც აქვს შეზღუდვები როგორციაა PIP(Picture-in-Picture) ან multi-window რეჟიმი. თუმცა ეს ამ წუთას ყველაზე პატარა სატკივარია. თუკი ზედა ფრაგმენტების პრობლემის გამო გადავწყვიტეთ რომ ექთივითდან დაგვეტესტა რაღაც ფუნქციონალი, მაშინ ამ ტესტებში პრობლემა არის რომ იზოლირებულია და ფრაგმენტზე არ გვაქ პირდაპირ წვდომა თუკი ჩვენ თვითონ არ გავუკეთებთ .commit() -ს. ასეთი რაღაცებისთვის და ნავიგაციისთვის კი ეს ხელსაწყო არ უნდა გამოვიყენოთ, უბრალოდ არ არის მისი მოვალეობა ასეთი სახის ტესტები. ტესტავს მხოლოდ იზოლირებულ ექთივითის და თავის საქმეს კარგად ასრულებს თუკი კოდი მართლაც ისე გვიწერია რომ ფრაგმენტებს და ექთივითებს არ აქვთ არაფერი მოვალეობა და არიან მხოლოდ ვიზუალის დასარენდერებლად რაც დამერწმუნებით legacy და დიდი კოდის ბაზაში იშვიათობაა.

ეს ყველაფერი რთულდება თუკი ჩვენი კლასები მკაცრად არიან დამოკიდებულნი ავტორიზაციაზე და მის გარეშე არ ჩაიტვირთებიან, არც კონკრეტულ მონაცემებზე ექნებათ წვდომა.

გადავიდეთ Espresso-ზე, ძველ და გამოცდილ ხელსაწყოზე. რათქმაუნდა Espresso სრულ კონტროლს გვაძლევს UI პროცესებზე თუმცა აქვს თავისი მინუსებიც. მაგალითისთვის აკლია სხვადასხვა ViewAction -ები თუმცა თავისი ფართო და ღია api-ს საშულებით მარტივად შეგვიძლია ავანაზღაუროთ. ზემოთ მაგალითის განსამტკიცებლად — არ აქვს NestedScrollView ვიჯეტის შიგნით სქროლის საშუალება. თუკი ჩვენს view იერარქიაში ვიზუალური შემოწმების გაკეთება გვინდა და ვიჯეტი ხედვის არეალის დაბლა არის, გვჭირდება დაბლა ჩასქროლვა წინააღდმეგ შემთხვევაში ჩვენი შემოწმება შეცდომით დასრულდება. isDisplayed() ჩვენი ვიჯეტის 90%-ს მაინც უნდა ხედავდეს. სამაგიეროდ შეგვიძლია ჩვენით გავაკეთოთ. არ ვამბობ რომ მომწონს ამის კეთება მაგრამ ალბათ ყველას Android ეკოსისტემამ მიგვაჩვია.

შესაბამისად აქამდე თუ გვქონდა პრობლემა ახლა ასე მოვაგვარებთ

Espresso-ს შიგნით უკვე შეგვიძლია ნავიგაციის გამოყენება და ნებისმიერი მანიპულაცია, შეგვიძლია მონაცემების ქსელიდან წამოღებაც, მაგალითად კონკრეტული activity-ს ჩატვირთამდე ავტორიზაცია და ტოკენის შენახვა. ზოგადად ასინქრონული ოპერაციები როგორც ბლოკირებადი ისე უნდა გაეშვას რაც დაბლოკავს ჩვენს thread-ს და მოგვიწევს ლოდინი. Espresso გვთავაზობს IdlingResource-ს რომელიც არის ერთ-ერთი ცუდი api გამოსაყენებლად. პირდაპირ გვთავაზობს ჩვენს production კოდში გავწეროთ IdlingResource-ის საკონტროლო კოდი რათა ტესტებმა დაინახონ სად იწყება და მთავრდება ასინქრონული ოპერაცია. ალტერნატიული გადაწყვეტები წერია დოკუმენტაციის ბოლოში თუმცა არცერთი გამოიყენება სახარბიელოდ. ამიტომ მე პირადად თითქმის არ ვიყენებ ამ ხელსაწყოს ამ სახით.

View-ზე მანიპულაციებისთვის IdlingResource-ს აქვს ასევე ძალიან ცუდი შეზღუდვა, რომლის შეცვლაც შეუძლებელია, კერძოდ თუკი გვინდა დაველოდოთ რომელიმე ვიჯეტს რომ გამოჩნდეს ხედვის არეში, ჩვენი IdlingResource 5 წამში ერთხელ შეამოწმებს და რაღაც დროის შემდგომ დასრულდება შეცდომით. 5 წამი ძალიან დიდი დროა UI ტესტებში რომლის შეცვლის შესაძლებლობის წართმევა ერთ-ერთი შეცდომაა api დიზაინში. ამიტომ მირჩევნია მქონდეს 1 ფუნქცია რომელიც ყოველ 100 მილიწამში მაგალითად შეამოწმებს view-ს და რაღაც დროის შემდგომ დაასრულებს მოქმედებას. საბოლოო ვარიანტში მაინც დაახლოებით იმას დაწერთ ქვემოთ ვლინკავ. ძალიან კარგი დამხმარე ხელსაწყოა, რომელიც მართალია ძალიან ძველია მაგრამ თავისუფლად შეგიძლიათ გადააკეთოთ თქვენს გემოვნებაზე.

ზოგადად უნდა შევთანხმდეთ რომ თვითონ UI არის რთული და არასტაბილური გასატესტი ამიტომაც ხელსაწყოები რომელიც ყველანაირ დახმარებას გვთავაზობენ შეიძლება არ აღმოჩნდეს 100% ის რაც გვინდა და დაგვჭირდეს საათები თავის მტვრევა UI ტესტებზე. ამიტომ გარდა იმისა რომ UI ტესტების გაშვება ძვირიანი ოპერაციაა (ნელია და სჭირდება ემულატორი/რეალური მოწყობილობა) ასევე სჭირდება დიდი დრო საწერად.

UIAutomator-თან პირდაპირი შეხება ხშირად არ მქონია რადგანაც ყველაზე ბოლო ხელსაწყოა ინსტრუმენტებიდან რომელიც მინდა გამოვიყენო კომპლექსური api-ს გამო. რეალურად საჭიროებაც არ არის თითქმის და მისი ტესტებიც დიდი გამოდის ზომაში. თუმცა ისეთ შემთხვევებში როდესაც cross-app პროცესების გატესტვა გინდათ შეუცვლელი ინსტრუმენტია.

ბონუსი

მინდა გაგიზიაროთ ერთ-ერთი აპრობირებული პრაქტიკა ფრაგმენტების გასატესტად. შეგვიძლია androidTest პაკეტში გავაკეთოთ 1 ცარიელი Activity, რომელიც შეასრულებს ფრაგმენტების კონტეინერის როლს და მას მივაბათ ჩვენი ფრაგმენტები. ახლა იკითხავთ რა განსხვავებაა ფრაგმენტების იზოლირებული სცენარებისგანო, თუმცა განსხვავება დიდია, აქ შეგვიძლია ეს კონტეინერი შევინარჩუნოთ მთელი ტესტის განმავლობაში და გამოვიყენოთ ფრაგმენტიდან ფრაგმენტზე ნავიგაციისთვის, shared viewmodel-ის მისაბმელად და ა.შ.

ყველაფერს აქვს თავისი უარყოფითი და დადებითი მხარე, ჩვენი მიზანია რომ შევაფასოთ მოთხოვნები და ვიპოვოთ კარგი ბალანსი.

მადლობა ამ ბლოგის შავი ვერსიის რევიზიისთვის: ნიკოლოზ ახვლედიანს

Written by

Software Engineer with 8 years of experience, specializing in Android development. Amateur cyclist and runner

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store