Ders 07 / 19
Arayüz Testleri
Uygulamayı ayrı bir süreç olarak sürüp düğmeye basan arayüz testlerinin kurulması ve beklenmesi; üç denetimin ve bekleme anının doğru ve kusurlu sürümü ayırıp ayırmadığının ölçümü.
Yayın Güncelleme
Bu derste
- Arayüz testinin uygulamayı ayrı bir süreç olarak nasıl sürdüğünü ve öğeleri nasıl bulduğunu anlatır
- Metne bağlı, varlığa bağlı ve sonuca bağlı üç denetimi ölçerek ayırır
- Düğmenin gerçekten sipariş gönderdiğini yalnız sonucu denetleyen testin gösterdiğini kanıtlar
- Bekleme anının aynı denetimi ayıran bir denetimden gürültülü bir denetime çevirdiğini ölçer
İçindekiler
Birim testi bir işlevi çağırır ve dönen değere bakar. Kullanıcı ise işlev çağırmaz; bir düğmeye basar. Düğmenin arkasında doğru kod çalışsa bile düğme ekrana bağlanmamış olabilir. Önceki derste (Birim Testleri) bir sınır kusurunun yalnız sınır değerini deneyen denetimle yakalandığını gördük. Bu derste kusur başka bir yerde: “Siparişi Onayla” düğmesi görünüyor, basılıyor, ama sipariş göndermiyor.
Ölçü ekseni yine ayrımdır: aynı denetimi doğru sürüme ve kusurlu sürüme uygularız. Ayıran denetim doğru sürümde geçer, kusurlu sürümde kalır. Kör denetim ikisinde de geçer. Gürültülü denetim ikisinde de kalır. Ters denetim doğru sürümde kalır, kusurlu sürümde geçer. Dört adı çıktıda ASCII olarak göreceğiz. Çekirdek önceki dersteki çekirdeğin aynısıdır ve yeniden yazılmıştır, çünkü her ders kendi başına çalışır.
// Separation.swift — bir denetimin dogru surumu kusurlu surumden ayirip ayirmadigini siniflandirir
struct Contrast<Output: CustomStringConvertible> {
let label: String
let correct: Output
let faulty: Output
let passesCorrect: Bool
let passesFaulty: Bool
var separation: String {
switch (passesCorrect, passesFaulty) {
case (true, false): return "ayiran"
case (true, true): return "kor"
case (false, false): return "gurultulu"
case (false, true): return "ters"
}
}
func describe() -> String {
let onCorrect = passesCorrect ? "gecti" : "kaldi"
let onFaulty = passesFaulty ? "gecti" : "kaldi"
print("\(label) | dogru=\(correct)/\(onCorrect) kusurlu=\(faulty)/\(onFaulty) -> \(separation)")
return separation
}
}
func contrast<Output>(_ label: String, correct: Output, faulty: Output, check: (Output) -> Bool) -> Contrast<Output> {
Contrast(label: label, correct: correct, faulty: faulty, passesCorrect: check(correct), passesFaulty: check(faulty))
}
func countSeparations(_ results: [String]) {
for name in ["ayiran", "kor", "gurultulu", "ters"] {
print("\(name): \(results.filter { $0 == name }.count)")
}
}
Contrast iki sürümün çıktısını ve denetimin o çıktılar üzerindeki kararını tutar; separation etiketi yalnız iki doğruluk değerinden türetir. contrast işlevi aynı kapanışı iki çıktıya uygular, countSeparations dört kategoriyi sayar. Kural nedeni: denetimin değeri geçmesinde değil, iki sürümü ayırmasında yatar.
Uygulamayı Sürmek
Arayüz testi uygulamanın kodunu çağırmaz. Test hedefi uygulamayı ayrı bir süreç olarak başlatır ve ekrana dışarıdan bir kullanıcı gibi dokunur. Bu yüzden testin içinden uygulamanın tür ve işlevlerine erişilmez; yalnız ekranda görünen öğelere erişilir. Sürücü XCUIApplication nesnesidir.
// siparis onayi arayuz testi — KAYNAK
import XCTest
final class CheckoutUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
func testConfirmButtonExists() {
let app = XCUIApplication()
app.launchArguments = ["--reset-orders"]
app.launch()
XCTAssertTrue(app.buttons["confirmButton"].exists)
}
func testConfirmAddsOrder() {
let app = XCUIApplication()
app.launchArguments = ["--reset-orders"]
app.launch()
app.buttons["confirmButton"].tap()
XCTAssertTrue(app.tables["orderList"].cells.element(boundBy: 0).waitForExistence(timeout: 5))
}
}
Bu parça çalıştırılmaz; bir simülatör ya da gerçek aygıt ister. continueAfterFailure = false ilk iddia tutmayınca testi durdurur; aksi hâlde boş bir ekrana tıklamaya devam eden test art arda anlamsız hatalar üretir. launchArguments uygulamaya testin başlangıç durumunu söyler: burada sipariş listesini boşaltan bir bayrak. Her test aynı durumdan başlamazsa öncekinin bıraktığı kayıt sonuçları değiştirir.
Öğeler üç yolla bulunur. Görünen metinle (app.buttons["Siparisi Onayla"]), sıra numarasıyla (element(boundBy:)) ya da erişilebilirlik tanımlayıcısıyla (accessibility identifier). Tanımlayıcı, öğeye uygulama kodunda verilmiş sabit bir addır ve metin değişse de aynı kalır. Yukarıdaki parçada düğme confirmButton tanımlayıcısıyla bulunuyor.
Beklemek
Ekran anında değişmez. Düğmeye basıldığında sipariş gönderilir, yanıt gelir, liste yenilenir; bu zincir bir süre alır. waitForExistence(timeout:) öğenin ekranda belirmesini en çok verilen süre kadar bekler ve Bool döndürür. Beklemeden doğrudan exists okumak, öğe henüz çizilmemişken yanlış cevap alma riskidir.
Bu derste süreleri ölçmeyeceğiz ve bir testin zaman aşımına ne kadar yaklaştığını göstermeyeceğiz. Zamanlama makineden makineye değişir; ona bağlı bir sayı basmak tekrarlanabilir olmaz. Bunun yerine modelde bekleme iki duruma ayrılır: düğmeye basıldıktan hemen sonraki ekran ve ekranın oturduktan sonraki hâli. Gerçek bir testte bu iki an arasında kaç milisaniye geçeceği ölçülmez; modelde yalnız “önce” ve “sonra” vardır.
Onay Ekranı Modeli
Aşağıdaki model, denetimlerin iki sürüme ne dediğini kurar; arayüz test çerçevesinin kendisi değildir. Ekran bir yapıdır. Düğmenin başlığı, bekleyen siparişler ve ekranda listelenmiş siparişler tutulur. tapConfirm düğmeye basmayı, settle ekranın oturmasını temsil eder. Doğru sürümde basış bir sipariş ekler; kusurlu sürümde düğmeye basmak hiçbir şey yapmaz.
// Checkout.swift — onay ekraninin durumu, dogru ve kusurlu surumu
struct CheckoutScreen {
let confirmTitle = "Siparisi Onayla"
private(set) var pending: [String] = []
private(set) var orders: [String] = []
mutating func tapConfirm(sending: Bool) {
if sending { pending.append("siparis-1") }
}
mutating func settle() {
orders += pending
pending = []
}
}
func screenAfterTap(sending: Bool, settled: Bool) -> CheckoutScreen {
var screen = CheckoutScreen()
screen.tapConfirm(sending: sending)
if settled { screen.settle() }
return screen
}
Ekran oturmuş durumda üç denetim uygulanır. İlki metne bağlıdır: düğmenin başlığını, uygulamanın eski sürümündeki yazımla (“Siparisi onayla”) karşılaştırır. Metin sonradan bir yazı düzeltmesiyle büyük harfle değişmiştir ve iki sürüm de yeni metni taşır. İkincisi varlığa bağlıdır: düğme ekranda var mı. Üçüncüsü sonuca bağlıdır: düğmeye basıldıktan sonra sipariş listesi bir sipariş uzamış mı.
// main.swift — uc denetim, iki surum
let correct = screenAfterTap(sending: true, settled: true)
let faulty = screenAfterTap(sending: false, settled: true)
let oldTitle = contrast("eski-baslik-metni", correct: correct.confirmTitle, faulty: faulty.confirmTitle, check: { $0 == "Siparisi onayla" }).describe()
let exists = contrast("dugme-var", correct: !correct.confirmTitle.isEmpty, faulty: !faulty.confirmTitle.isEmpty, check: { $0 }).describe()
let listGrew = contrast("siparis-listesi-uzadi", correct: correct.orders.count, faulty: faulty.orders.count, check: { $0 == 1 }).describe()
countSeparations([oldTitle, exists, listGrew])
eski-baslik-metni | dogru=Siparisi Onayla/kaldi kusurlu=Siparisi Onayla/kaldi -> gurultulu dugme-var | dogru=true/gecti kusurlu=true/gecti -> kor siparis-listesi-uzadi | dogru=1/gecti kusurlu=0/kaldi -> ayiran ayiran: 1 kor: 1 gurultulu: 1 ters: 0
eski-baslik-metni gürültülü: iki sürüm de “Siparisi Onayla” başlığını taşır ve denetim eski yazımı bekler, o yüzden ikisini de reddeder. Düğme çalışıyordur, test kızar. dugme-var kör: düğme iki sürümde de ekranda. siparis-listesi-uzadi ayıran: doğru sürümde liste bir sipariş taşır, kusurlu sürümde sıfır taşır.
Bu üç denetimin üçü de arayüz testinde yazılabilir ve üçü de bir test raporunda aynı görünür. Biri uygulamadaki kopyalama değişikliğinde boş yere kırılır, biri düğme hiç çalışmasa da yeşil kalır, biri ise düğmenin işini yaptığını söyler. Fark, denetimin neye baktığıdır: metne mi, varlığa mı, sonuca mı.
TS1. Ekran modeli tek bir sipariş ekler. Birden çok sipariş, silme ya da iptal ayrıca ölçülmelidir.
TS2. eski-baslik-metni denetimini gürültülü yapan şey iki sürümün de yeni metni taşımasıdır. Metin yalnız kusurlu sürümde değişseydi aynı denetim ters ya da ayıran olabilirdi.
Metne Bağlı Denetim
Metne bağlı bir test düğmeyi görünen metniyle bulur. Bu rahattır, çünkü ekranda ne yazıyorsa testte de odur. Ama metin pazarlamanın, çevirinin ya da yazı düzeltmesinin elindedir. Başlık değiştiğinde uygulama doğru çalışmaya devam eder, test ise kızar. Bu tür bir kırılma bir kusuru göstermez; bakım yükü yaratır.
Üstelik çok dilli bir uygulamada aynı düğmenin metni dile göre değişir. Metne bağlı bir test yalnız yazıldığı dilde çalışır. Test planının ikinci bir dil yapılandırması eklendiğinde metne bağlı testler ikinci dilde kalır; bu konunun sonraki dersinde ölçeceğiz. Tanımlayıcıyla bulunan düğme ise her dilde aynı adı taşır.
Metni testten tamamen atmak da çözüm değildir. Bazı testler metnin kendisini sınar: sipariş onaylandığında ekranda “Siparişiniz alındı” yazması bir davranıştır. Fark şuradadır: bir öğeyi bulmak için metin kullanmak bakım yükü getirir, bir davranışı doğrulamak için kullanmak getirmez.
Sonuca Bağlı Denetim
siparis-listesi-uzadi ayırdı, çünkü düğmenin ürettiği etkiye baktı. Düğme gerçekten çalışıyorsa liste uzar; çalışmıyorsa uzamaz. Bir arayüz testi yazarken ilk soru “düğme var mı” değil “düğmeye basınca ne olmalı” olmalıdır.
Sonuca bağlı denetimin bedeli vardır. Sipariş listesinin ekranda göründüğü yer değişirse, yeni bir sekmeye taşınırsa ya da tablo bir koleksiyona dönüşürse test yine kırılır. Bu yüzden tanımlayıcı sabit tutulur ve ekranın yapısı değişirken tanımlayıcı taşınır. Sonuca bağlı denetim ayırır ama bakımı da ister.
Arayüz testleri yavaştır. Her biri uygulamayı başlatır, ekranı sürer ve bitirir. Bu yüzden sayıları az, kapsamları geniş tutulur: birim testleri kod mantığını sınar, arayüz testleri uçtan uca birkaç temel yolu sınar. Bir sınır kusuru için arayüz testi yazmak yanlış araçtır; bir bağlantı kusuru için birim testi yazmak da öyle.
Sınırlayıcı Ölçüm
Aynı sonuca bağlı denetim, bekleme anına göre farklı davranır. Aşağıda siparis-listesi-uzadi denetimi iki ekranda uygulanır: düğmeye basılır basılmaz bakılan ekranda ve ekran oturduktan sonra bakılan ekranda. İlkine anlik-bakis, ikincisine bekledikten-sonra adını verdik.
// main.swift — ayni denetim, iki bakis ani
let immediateCorrect = screenAfterTap(sending: true, settled: false)
let immediateFaulty = screenAfterTap(sending: false, settled: false)
let settledCorrect = screenAfterTap(sending: true, settled: true)
let settledFaulty = screenAfterTap(sending: false, settled: true)
let immediate = contrast("anlik-bakis", correct: immediateCorrect.orders.count, faulty: immediateFaulty.orders.count, check: { $0 == 1 }).describe()
let afterWait = contrast("bekledikten-sonra", correct: settledCorrect.orders.count, faulty: settledFaulty.orders.count, check: { $0 == 1 }).describe()
countSeparations([immediate, afterWait])
anlik-bakis | dogru=0/kaldi kusurlu=0/kaldi -> gurultulu bekledikten-sonra | dogru=1/gecti kusurlu=0/kaldi -> ayiran ayiran: 1 kor: 0 gurultulu: 1 ters: 0
Aynı denetim, ekran oturmadan uygulandığında gürültülü oldu: doğru sürüm bile sıfır sipariş gösterir ve test kalır. Bekledikten sonra aynı denetim ayırandır. Denetimin değeri yalnız neye baktığına değil, ne zaman baktığına da bağlıdır.
Modelde iki an kesin ve belirlenimlidir: önce ve sonra. Gerçek bir uygulamada oturma anı değişir; test bazı koşumlarda beklemeyi yetiştirir, bazılarında yetiştiremez. Böyle sonuçları belirsiz olan teste kırılgan test (flaky test) denir ve bu dersin modelinde basılmaz; çünkü model her koşumda aynı sonucu verir ve belirsizliği temsil etmez. Kırılgan bir testi çözmek için waitForExistence(timeout:) gibi açık beklemeler kullanılır; keyfi bir süre uyumak ise sorunu gizler.
TS3. Model oturmayı tek bir adım sayar. Gerçekte liste birden çok adımda yenilenebilir ve ara durumlar görülebilir.
TS4. anlik-bakis denetiminin gürültülü çıkması modelin iki ekranı da aynı anda boş saymasından gelir. Gerçek bir koşumda ekran bazen hazır olabilir ve sonuç değişir.
Sık Yapılan Hatalar
- Düğmeyi yalnız metniyle bulmak. Başlık değiştiğinde ya da uygulama başka dilde açıldığında test kırılır.
- Yalnız düğmenin ekranda olduğunu denetlemek. Kör kalır ve işlevsiz düğmeyi kaçırır.
- Sabit süre uyuyarak beklemek. Süre kısaysa test kırılgan olur, uzunsa gereksiz yavaşlar.
- Her testi uygulamanın önceki koşumdan kalan durumundan başlatmak. Başlangıç durumu bayraklarla sıfırlanmalıdır.
- Bir sınır kusurunu arayüz testiyle aramak. Bunun için birim testi daha hızlı ve daha kesindir.
Özet
- Arayüz testi uygulamayı ayrı bir süreç olarak sürer ve ekrana yalnız görünen öğeler üzerinden erişir.
- Öğeler metinle, sıra numarasıyla ya da erişilebilirlik tanımlayıcısıyla bulunur; tanımlayıcı metin değişse de aynı kalır.
- Üç denetimden biri gürültülü, biri kör, biri ayıran çıktı; yalnız sonuca bağlı denetim düğmenin sipariş gönderdiğini gösterdi.
- Aynı denetim, ekran oturmadan uygulandığında gürültülü oldu; bekleme anı denetimin kategorisini değiştirdi.
- Sonuç belirsiz olan kırılgan testler modelde basılmaz; çözümleri açık beklemedir, keyfi süre değil.
- Bu dersin ayrımı: gürültülü.
Sonraki Adım
Bu derste denetimleri tek tek yazdık. Sıradaki derste Test Planları ve Kapsam, tüm testleri bir plan altında toplayıp hangi satırların koştuğunu sayacak ve koşan bir satırın doğru olduğunu söylemediğini ölçecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.