Bir Sıçrama EarlGrey – Toptal Talent Uygulamasını Kullanıcı Arayüzü Test Ediyor
Yayınlanan: 2022-03-11Bir test kullanıcısı olarak işinizi daha verimli ve hızlı hale getirmek için yapabileceğiniz en önemli şeylerden biri, test ettiğiniz uygulamayı otomatik hale getirmektir. Tüm testleri her gün, bazen günde birkaç kez çalıştırmanız ve uygulama koduna gönderilen her değişikliği test etmeniz gerekeceğinden, yalnızca manuel testlere güvenmek mümkün değildir.
Bu makale, ekibimizin, iOS Toptal Talent uygulamasını otomatikleştirme bağlamında bizim için en iyi sonucu veren araç olarak Google'ın EarlGrey 1.0'ını belirleme yolculuğunu açıklayacaktır. Bunu kullanıyor olmamız, EarlGrey'in herkes için en iyi test aracı olduğu anlamına gelmiyor - sadece ihtiyaçlarımıza uyan bir araç.
Neden EarlGrey'e Geçtik?
Yıllar içinde ekibimiz hem iOS hem de Android'de farklı mobil uygulamalar geliştirdi. Başlangıçta, tek bir test seti yazmamıza ve bunları farklı mobil işletim sistemlerinde yürütmemize izin verecek bir çapraz platform UI test aracı kullanmayı düşündük. İlk olarak, mevcut en popüler açık kaynak seçeneği olan Appium ile gittik.
Ancak zaman geçtikçe, Appium sınırlamaları giderek daha belirgin hale geldi. Bizim durumumuzda, Appium'un iki ana dezavantajı şunlardı:
- Çerçevenin şüpheli kararlılığı birçok test hatasına neden oldu.
- Nispeten yavaş güncelleme süreci çalışmalarımızı engelledi.
İlk Appium eksikliğini azaltmak için testleri daha kararlı hale getirmek için her türlü kod düzeltmesi ve hack'i yazdık. Ancak, ikincisini ele almak için yapabileceğimiz hiçbir şey yoktu. Her yeni iOS veya Android sürümü çıktığında, Appium'un yetişmesi uzun zaman aldı. Ve çoğu zaman, birçok hata nedeniyle ilk güncelleme kullanılamazdı. Sonuç olarak, çalışan bir Appium güncellemesi kullanıma sunulana kadar testlerimizi genellikle daha eski bir platform sürümünde yürütmeye devam etmek veya bunları tamamen kapatmak zorunda kaldık.
Bu yaklaşım ideal olmaktan uzaktı ve bu sorunlar nedeniyle, ayrıntılı olarak ele almayacağımız ek sorunlarla birlikte alternatifler aramaya karar verdik. Yeni bir test aracı için en önemli kriterler, artan kararlılık ve daha hızlı güncellemelerdi . Biraz araştırma yaptıktan sonra, her platform için yerel test araçlarını kullanmaya karar verdik.
Böylece, Android projesi için Espresso'ya ve iOS geliştirme için EarlGrey 1.0'a geçtik. Geriye dönüp baktığımızda, bunun iyi bir karar olduğunu söyleyebiliriz. Her platform için bir tane olmak üzere iki farklı test seti yazma ve sürdürme ihtiyacı nedeniyle “kaybedilen” zaman, bu kadar çok kesintili testin araştırılmasına gerek olmaması ve sürüm güncellemelerinde herhangi bir kesinti olmaması nedeniyle telafi edilmekten daha fazlasıydı.
Yerel Proje Yapısı
Çerçeveyi, geliştirmekte olduğunuz uygulamayla aynı Xcode projesine dahil etmeniz gerekecek. Böylece, UI testlerini barındırmak için kök dizinde bir klasör oluşturduk. Test çerçevesi kurulurken EarlGrey.swift
dosyasının oluşturulması zorunludur ve içeriği önceden tanımlanmıştır.

EarlGreyBase
, tüm test sınıfları için üst sınıftır. setUp
genişletilmiş genel tearDown
ve yırtma yöntemlerini XCTestCase
. setUp
'da, genellikle testlerin çoğu tarafından kullanılacak olan saplamaları yükleriz (daha sonra saplama hakkında daha fazlası) ve ayrıca testlerin kararlılığını artırdığını fark ettiğimiz bazı yapılandırma bayrakları ayarlarız:
// Turn off EarlGrey's network requests tracking since we don't use it and it can block tests execution GREYConfiguration.sharedInstance().setValue([".*"], forConfigKey: kGREYConfigKeyURLBlacklistRegex) GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeyAnalyticsEnabled)
Sayfa Nesnesi tasarım modelini kullanıyoruz - uygulamadaki her ekranın, tüm UI öğelerinin ve olası etkileşimlerinin tanımlandığı ilgili bir sınıfı vardır. Bu sınıfa "sayfa" denir. Test yöntemleri, sayfalardan ayrı dosyalarda ve sınıflarda bulunan özelliklere göre gruplandırılmıştır.
Size her şeyin nasıl görüntülendiği hakkında daha iyi bir fikir vermek için, Uygulamamızdaki Oturum Açma ve Parolamı Unuttum ekranları bu şekilde görünür ve sayfa nesneleri tarafından nasıl temsil edilirler.

Yazının ilerleyen bölümlerinde Login sayfası nesnesinin kod içeriklerini sunacağız.
Özel Yardımcı Program Yöntemleri
EarlGrey'in test eylemlerini uygulama ile senkronize etme şekli her zaman mükemmel değildir. Örneğin, UI hiyerarşisinde henüz yüklenmemiş bir düğmeyi tıklamayı deneyerek testin başarısız olmasına neden olabilir. Bu sorunu önlemek için, öğelerle etkileşime geçmeden önce istenen durumda görünene kadar beklemek için özel yöntemler oluşturduk.
İşte birkaç örnek:
static func asyncWaitForVisibility(on element: GREYInteraction) { // By default, EarlGrey blocks test execution while // the app is animating or doing anything in the background. //https://github.com/google/EarlGrey/blob/master/docs/api.md#synchronization GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeySynchronizationEnabled) element.assert(grey_sufficientlyVisible()) GREYConfiguration.sharedInstance().setValue(true, forConfigKey: kGREYConfigKeySynchronizationEnabled) } static func waitElementVisibility(for element: GREYInteraction, timeout: Double = 15.0) -> Bool { GREYCondition(name: "Wait for element to appear", block: { var error: NSError? element.assert(grey_notNil(), error: &error) return error == nil }).wait(withTimeout: timeout, pollInterval: 0.5) if !elementVisible(element) { XCTFail("Element didn't appear") } return true }
EarlGrey'in kendi başına yapmadığı bir diğer şey, istenen öğe görünene kadar ekranı kaydırmaktır. Bunu şu şekilde yapabiliriz:
static func elementVisible(_ element: GREYInteraction) -> Bool { var error: NSError? element.assert(grey_notVisible(), error: &error) if error != nil { return true } else { return false } } static func scrollUntilElementVisible(_ scrollDirection: GREYDirection, _ speed: String, _ searchedElement: GREYInteraction, _ actionElement: GREYInteraction) -> Bool { var swipes = 0 while !elementVisible(searchedElement) && swipes < 10 { if speed == "slow" { actionElement.perform(grey_swipeSlowInDirection(scrollDirection)) } else { actionElement.perform(grey_swipeFastInDirection(scrollDirection)) } swipes += 1 } if swipes >= 10 { return false } else { return true } }
EarlGrey'in API'sinde eksik olan diğer yardımcı yöntemler, öğeleri saymak ve metin değerlerini okumaktır. Bu yardımcı programların kodu GitHub'da mevcuttur: burada ve burada.
API Çağrılarını Engelleme
Arka uç sunucu sorunlarından kaynaklanan yanlış test sonuçlarından kaçındığımızdan emin olmak için sunucu çağrılarını taklit etmek için OHHTTPStubs kitaplığını kullanırız. Ana sayfalarındaki belgeler oldukça basittir, ancak GraphQL API kullanan uygulamamızda yanıtları nasıl sapladığımızı göstereceğiz.

class StubsHelper { static let testURL = URL(string: "https://[our backend server]")! static func setupOHTTPStub(for request: StubbedRequest, delayed: Bool = false) { stub(condition: isHost(testURL.host!) && hasJsonBody(request.bodyDict())) { _ in let fix = appFixture(forRequest: request) if delayed { return fix.requestTime(0.1, responseTime: 7.0) } else { return fix } } } static let stubbedEmail = "[email protected]" static let stubbedPassword = "password" enum StubbedRequest { case login func bodyDict() -> [String: Any] { switch self { case .login: return EmailPasswordSignInMutation( email: stubbedTalentLogin, password: stubbedTalentPassword ).makeBodyIdentifier() } } func statusCode() -> Int32 { return 200 } func jsonFileName() -> String { let fileName: String switch self { case .login: fileName = "login" } return "\(fileName).json" } } private extension GraphQLOperation { func makeBodyIdentifier() -> [String: Any] { let body: GraphQLMap = [ "query": queryDocument, "variables": variables, "operationName": operationName ] // Normalize values like enums here, otherwise body comparison will fail guard let normalizedBody = body.jsonValue as? [String: Any] else { fatalError() } return normalizedBody } }
Saplamanın yüklenmesi, setupOHTTPStub
yöntemi çağrılarak gerçekleştirilir:
StubsHelper.setupOHTTPStub(for: .login)
Her Şeyi Bir Araya Getirmek
Bu bölüm, gerçek bir uçtan uca oturum açma testi yazmak için yukarıda açıklanan tüm ilkeleri nasıl kullandığımızı gösterecektir.
import EarlGrey final class LoginPage { func login() -> HomePage { fillLoginForm() loginButton().perform(grey_tap()) return HomePage() } func fillLoginForm() { ElementsHelper.waitElementVisibility(emailField()) emailField().perform(grey_replaceText(StubsHelper.stubbedTalentLogin)) passwordField().perform(grey_tap()) passwordField().perform(grey_replaceText(StubsHelper.stubbedTalentPassword)) } func clearAllInputs() { if ElementsHelper.elementVisible(passwordField()) { passwordField().perform(grey_tap()) passwordField().perform(grey_replaceText("")) } emailField().perform(grey_tap()) emailField().perform(grey_replaceText("")) } } private extension LoginPage { func emailField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement(with: grey_accessibilityLabel("Email"), file: file, line: line) } func passwordField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement( with: grey_allOf([ grey_accessibilityLabel("Password"), grey_sufficientlyVisible(), grey_userInteractionEnabled() ]), file: file, line: line ) } func loginButton(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement(with: grey_accessibilityID("login_button"), file: file, line: line) } } class BBucketTests: EarlGreyBase { func testLogin() { StubsHelper.setupOHTTPStub(for: .login) LoginPage().clearAllInputs() let homePage = LoginPage().login() GREYAssertTrue( homePage.assertVisible(), reason: "Home screen not displayed after successful login" ) } }
CI'de Testleri Çalıştırma
Jenkins'i sürekli entegrasyon sistemimiz olarak kullanıyoruz ve her çekme talebindeki her taahhüt için UI testleri yapıyoruz.
Testleri CI'de yürütmek ve raporlar oluşturmak için fastlane scan
kullanıyoruz. Başarısız testler için bu raporlara ekran görüntülerinin eklenmesi yararlıdır. Ne yazık ki, scan
bu işlevi sağlamıyor, bu yüzden onu özel olarak yapmak zorunda kaldık.
tearDown()
işlevinde, testin başarısız olup olmadığını tespit eder ve başarısız olursa iOS simülatörünün ekran görüntüsünü kaydederiz.
import EarlGrey import XCTest import UIScreenCapture override func tearDown() { if testRun!.failureCount > 0 { // name is a property of the XCTest instance // https://developer.apple.com/documentation/xctest/xctest/1500990-name takeScreenshotAndSave(as: name) } super.tearDown() } func takeScreenshotAndSave(as testCaseName: String) { let imageData = UIScreenCapture.takeSnapshotGetJPEG() let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let filePath = "\(paths[0])/\(testCaseName).jpg" do { try imageData?.write(to: URL.init(fileURLWithPath: filePath)) } catch { XCTFail("Screenshot not written.") } }
Ekran görüntüleri Simulator klasörüne kaydedilir ve bunları yapı yapıları olarak eklemek için oradan getirmeniz gerekir. CI betiklerimizi yönetmek için Rake
kullanıyoruz. Test yapılarını şu şekilde topluyoruz:
def gather_test_artifacts(booted_sim_id, destination_folder) app_container_on_sim = `xcrun simctl get_app_container #{booted_sim_id} [your bundle id] data`.strip FileUtils.cp_r "#{app_container_on_sim}/Documents", destination_folder end
Önemli Çıkarımlar
iOS testlerinizi otomatikleştirmenin hızlı ve güvenilir bir yolunu arıyorsanız, EarlGrey'den başkasına bakmayın. Google tarafından geliştirilir ve sürdürülür (daha fazlasını söylememe gerek var mı?) ve birçok açıdan günümüzde mevcut olan diğer araçlardan üstündür.
Test kararlılığını artırmak için faydalı yöntemler hazırlamak için çerçeveyle biraz uğraşmanız gerekecek. Bunu yapmak için özel yardımcı program yöntemleri örneklerimizle başlayabilirsiniz.
Arka uç sunucusu, beklediğiniz tüm test verilerine sahip olmadığından, testlerinizin başarısız olmayacağından emin olmak için saplanmış veriler üzerinde test yapmanızı öneririz. İşi halletmek için OHHTTPStubs
veya benzer bir yerel web sunucusu kullanın.
Testlerinizi CI'de çalıştırırken, hata ayıklamayı kolaylaştırmak için başarısız olan durumlar için ekran görüntüleri sağladığınızdan emin olun.
Neden henüz EarlGrey 2.0'a geçmediğimizi merak ediyor olabilirsiniz ve işte size kısa bir açıklama. Yeni sürüm geçen yıl yayınlandı ve v1.0'a göre bazı geliştirmeler vaat ediyor. Ne yazık ki, EarlGrey'i benimsediğimizde v2.0 özellikle kararlı değildi. Bu nedenle henüz v2.0'a geçmedik. Ancak ekibimiz, gelecekte altyapımızı taşıyabilmemiz için yeni sürüm için bir hata düzeltmesini sabırsızlıkla bekliyor.
Çevrimiçi kaynaklar
EarlGrey'in GitHub ana sayfasındaki Başlarken kılavuzu, projeniz için test çerçevesini düşünüyorsanız, başlamak istediğiniz yerdir. Orada, kullanımı kolay bir kurulum kılavuzu, aracın API belgeleri ve testlerinizi yazarken kullanımı kolay bir şekilde çerçevenin tüm yöntemlerini listeleyen kullanışlı bir hile sayfası bulacaksınız.
iOS için otomatik testler yazma hakkında ek bilgi için önceki blog gönderilerimizden birine de göz atabilirsiniz.