ChatGPT ile IOS Test Otomasyon Denemesi

Uguraydn
Kariyer.net Tech
Published in
7 min readJun 28, 2024

Yazılım geliştirme süreçlerinde kalite, güvenilirlik ve hız büyük önem taşır. Geleneksel test yöntemleri zaman alıcı ve maliyetli olabilirken, üretken yapay zeka (AI) destekli test otomasyonu, bu süreçleri daha verimli ve etkili hale getirmektedir. Bu makalede, yapay zeka ile ios test otomasyonunda deneyimlediğimiz bazı yöntemleri paylaşacağım.

İlk olarak yapay zekaya “XCUITest ile login sayfasının test otomasyon case’lerini yazabilir misin? ” diye sordum ?

Paylaştığı kodlarda, başarılı bir şekilde giriş yapılması, giriş sırasında hata oluşması ve giriş alanlarına geçersiz karakter girişi yapılması gibi kontrollerin kodlarını paylaştı.

import XCTest

class LoginUITests: XCTestCase {

let app = XCUIApplication()

override func setUpWithError() throws {
continueAfterFailure = false
app.launch()
}

func testSuccessfulLogin() throws {
let usernameTextField = app.textFields["username"]
let passwordTextField = app.secureTextFields["password"]
let loginButton = app.buttons["loginButton"]

usernameTextField.tap()
usernameTextField.typeText("validUsername")

passwordTextField.tap()
passwordTextField.typeText("validPassword")

loginButton.tap()

// Giriş sonrası beklenen ekran elemanını kontrol et
let homeScreenElement = app.otherElements["homeScreenIdentifier"]
XCTAssertTrue(homeScreenElement.exists)
}
}
func testFailedLoginInvalidPassword() throws {
let usernameTextField = app.textFields["username"]
let passwordTextField = app.secureTextFields["password"]
let loginButton = app.buttons["loginButton"]

usernameTextField.tap()
usernameTextField.typeText("validUsername")

passwordTextField.tap()
passwordTextField.typeText("invalidPassword")

loginButton.tap()

// Hata mesajını kontrol et
let errorMessage = app.staticTexts["errorMessageIdentifier"]
XCTAssertTrue(errorMessage.exists)
}
func testInvalidCharactersInEmailField() {
let emailTextField = app.textFields["emailTextField"]
let passwordSecureField = app.secureTextFields["passwordSecureField"]
let loginButton = app.buttons["loginButton"]

emailTextField.tap()
emailTextField.typeText("invalid@char!acters.com")

passwordSecureField.tap()
passwordSecureField.typeText("somePassword")

loginButton.tap()

let emailFormatErrorMessage = app.staticTexts["Invalid email format"]
XCTAssertTrue(emailFormatErrorMessage.exists, "Error message is displayed for invalid characters in email.")
}

Sonrasında bir test senaryosu oluşturdum ve bize aşağıdaki çıktıları verdi.

Test senaryomuz şu şekildeydi:

1) Kariyer.net uygulaması açılır
2) Ana sayfada olduğumuzu gösteren Kariyer.net logosunun göründüğü kontrol edilir.
3) Sağ üst köşede bulunan "Giriş Yap" butonuna tıklanır.
4) Login sayfasındaki gerekli kontroller yapılır.
5) E-posta alanına "ugurtest@gmail.com" yazılır
6) Şifre alanına "deneme123" yazılarak "Giriş Yap" butonuna tıklanır.
7) Daha sonra, giriş yapıldığına dair "Hoşgeldin Uğur" yazısının göründüğü kontrol edilir.

Test senaryolarını verdiğimiz senaryolarda da kodları üretmekte başarılı olduğunu gördüm. Ekstradan bizlere “Her UI elemanının erişilebilirlik identifier’ının doğru tanımlanmış olması gerekmektedir. Eğer erişilebilirlik identifier’ları farklı ise, bu değerleri uygulamanızın gerçek erişilebilirlik identifier’ları ile değiştirmelisiniz.” şeklinde bilgilendirici mesaj eklediğini gördüm.

import XCTest

class KariyerNetUITests: XCTestCase {

let app = XCUIApplication()

override func setUp() {
super.setUp()
continueAfterFailure = false
app.launch()
}

override func tearDown() {
super.tearDown()
}

func testLoginFlow() {
// 1) Kariyer.net uygulaması açılır (app.launch() çağrılmıştır)

// 2) Ana sayfada olduğumuzu gösteren kariyer.net logosunun göründüğü kontrol edilir.
let kariyerNetLogo = app.images["KariyerNetLogo"] // Kariyer.net logosunun accessibilityIdentifier'ını kontrol edin
XCTAssertTrue(kariyerNetLogo.exists, "Kariyer.net logosu ana sayfada görünmüyor.")

// 3) Sağ üst köşede bulunan "Giriş Yap" butonuna tıklanır.
let girisYapButton = app.buttons["Giriş Yap"]
XCTAssertTrue(girisYapButton.exists, "Giriş Yap butonu görünmüyor.")
girisYapButton.tap()

// 4) Login sayfasındaki gerekli kontroller yapılır.
let emailField = app.textFields["emailField"] // Eposta alanı için accessibilityIdentifier'ı kontrol edin
XCTAssertTrue(emailField.exists, "Eposta alanı görünmüyor.")

let passwordField = app.secureTextFields["passwordField"] // Şifre alanı için accessibilityIdentifier'ı kontrol edin
XCTAssertTrue(passwordField.exists, "Şifre alanı görünmüyor.")

// 5) Eposta alanına "ugurtest@gmail.com" yazılır.
emailField.tap()
emailField.typeText("ugurtest@gmail.com")

// 6) Şifre alanına "deneme123" yazılarak "Giriş Yap" butonuna tıklanır.
passwordField.tap()
passwordField.typeText("deneme123")

let loginButton = app.buttons["LoginButton"] // Login butonu için accessibilityIdentifier'ı kontrol edin
XCTAssertTrue(loginButton.exists, "Giriş Yap butonu görünmüyor.")
loginButton.tap()

// 7) Daha sonra, giriş yapıldığına dair "Hoşgeldin Uğur" yazısının göründüğü kontrol edilir.
let welcomeMessage = app.staticTexts["Hoşgeldin Uğur"] // Hoşgeldin mesajı için accessibilityIdentifier'ı kontrol edin
XCTAssertTrue(welcomeMessage.exists, "Hoşgeldin Uğur mesajı görünmüyor.")
}
}

Daha sonrasında ekran görüntüleri vererek kendisine bu sayfaların test otomasyon caselerini yazmasını istedim. Paylaştığım ekran görüntüleri şu şekilde oldu ve ekstradan şu notları ekledim.

Bu 3 sayfanın test otomasyon caselerini XCUITest kullanarak yazar mısın? POM modelini kullanmanı istiyorum. Uygulama ilk açıldığında Anasayfa(1.jpeg) açılır. Anasayfada Giriş Yap’a tıklayınca Login(2.jpeg) sayfası açılır. Login sayfasında Şifremi unuttum a tıklayınca Şifremi Unuttum(3.jpeg) sayfası açılır

İlk başta ürettiği kodlarda , test class’larında olmasını istediğim assert’ler ve tıklamalar page’lerdeydi . Bazı sınıflarda kod tekrarları vardı . Daha sonrasında kendisine şu şekilde ifade edip tekrardan çıktı üretmesini istedim.

Pom yapını beğenmedim. Page class’larında sadece o sayfaya ait elementler bulunsun. Bütün testler test class’larında bulunsun. App launch etme gibi ortak kullanılan kodları base class’ı üretip ortak bir class tan çekmeni istiyorum

Bu cümlelerden sonra aşağıdaki kodları üretip sonrasında bazı önemli maddeleri paylaştı:

  • BaseTest sınıfı, uygulamanın başlatılması gibi ortak ayarları içerir.
  • HomePage, LoginPage, ve ForgotPasswordPage sınıfları, sadece o sayfaya ait elementleri içerir.
  • LoginTests sınıfı ise test senaryolarını içerir ve her bir sayfa nesnesini kullanarak test adımlarını gerçekleştirir.

Bu şekilde, POM yapısını daha sade ve anlaşılır hale getirmiş oldu. Testlerin ve sayfa elementlerinin bakımı ve güncellenmesi de daha kolay olacak.

import XCTest

class BaseTest: XCTestCase {
var app: XCUIApplication!

override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
}
import XCTest

class HomePage {
let app: XCUIApplication

init(app: XCUIApplication) {
self.app = app
}

var kariyerNetLogo: XCUIElement {
return app.images["kariyerNetLogo"]
}

var girisYapButton: XCUIElement {
return app.buttons["girisYap"]
}
}
import XCTest

class LoginPage {
let app: XCUIApplication

init(app: XCUIApplication) {
self.app = app
}

var emailTextField: XCUIElement {
return app.textFields["email"]
}

var passwordSecureTextField: XCUIElement {
return app.secureTextFields["password"]
}

var loginButton: XCUIElement {
return app.buttons["login"]
}

var forgotPasswordLink: XCUIElement {
return app.staticTexts["Şifremi Unuttum"]
}
}
import XCTest

class ForgotPasswordPage {
let app: XCUIApplication

init(app: XCUIApplication) {
self.app = app
}

var resetPasswordButton: XCUIElement {
return app.buttons["Şifremi Yenile"]
}

var emailOrUsernameField: XCUIElement {
return app.textFields["Kullanıcı Adı veya E-posta"]
}
}
import XCTest

class LoginTests: BaseTest {
var homePage: HomePage!
var loginPage: LoginPage!
var forgotPasswordPage: ForgotPasswordPage!

override func setUpWithError() throws {
try super.setUpWithError()

homePage = HomePage(app: app)
loginPage = LoginPage(app: app)
forgotPasswordPage = ForgotPasswordPage(app: app)
}

func testLoginFlow() throws {
// 1. Anasayfa doğrulama
XCTAssertTrue(homePage.kariyerNetLogo.exists, "Kariyer.net logosu anasayfada görünmüyor.")

// 2. Giriş yap butonuna tıklama
homePage.girisYapButton.tap()

// 3. Login sayfası doğrulama
XCTAssertTrue(loginPage.emailTextField.exists, "Eposta alanı görünmüyor.")
XCTAssertTrue(loginPage.passwordSecureTextField.exists, "Şifre alanı görünmüyor.")
XCTAssertTrue(loginPage.loginButton.exists, "Giriş yap butonu görünmüyor.")

// 4. Eposta ve şifre girip giriş yapma
loginPage.emailTextField.tap()
loginPage.emailTextField.typeText("ugurtest@gmail.com")

loginPage.passwordSecureTextField.tap()
loginPage.passwordSecureTextField.typeText("deneme123")

loginPage.loginButton.tap()

// 5. Başarılı login sonrası hoşgeldin yazısı doğrulama
let hosgeldinLabel = app.staticTexts["hosgeldinUgur"]
XCTAssertTrue(hosgeldinLabel.waitForExistence(timeout: 5), "Hoşgeldin Uğur yazısı bulunamadı.")
}

func testForgotPasswordFlow() throws {
// 1. Anasayfa doğrulama
XCTAssertTrue(homePage.kariyerNetLogo.exists, "Kariyer.net logosu anasayfada görünmüyor.")

// 2. Giriş yap butonuna tıklama
homePage.girisYapButton.tap()

// 3. Login sayfası doğrulama
XCTAssertTrue(loginPage.emailTextField.exists, "Eposta alanı görünmüyor.")
XCTAssertTrue(loginPage.passwordSecureTextField.exists, "Şifre alanı görünmüyor.")
XCTAssertTrue(loginPage.loginButton.exists, "Giriş yap butonu görünmüyor.")

// 4. Şifremi unuttum linkine tıklama
loginPage.forgotPasswordLink.tap()

// 5. Şifremi unuttum sayfası doğrulama
XCTAssertTrue(forgotPasswordPage.emailOrUsernameField.exists, "Kullanıcı Adı veya E-posta alanı görünmüyor.")
XCTAssertTrue(forgotPasswordPage.resetPasswordButton.exists, "Şifremi Yenile butonu görünmüyor.")
}
}

Son olarak kendisine sadece IPA dosyasını verdim. Daha sonrasında Hesabım tabında Ayarlar’ın içerisinde Kişisel Bilgiler sayfası olduğunu belirttim ve Kişisel bilgiler sayfasının kodlarını üretmesini söyledim.

Kişisel Bilgiler sayfasında İsim, Soy isim, Yaşadığın yer, Doğum tarihi ve Kaydet olmasına rağmen İsim, Soy isim ve Kaydet için çıktı üretti. Yaşadığın yer ve Doğum Tarihi yerine E-posta olarak üretmesi oldukça ilginçti.

Sayfanın tasarımını merak edenler için ekran görüntüsünü aşağıya ekledim. Bu ekran görüntüsünü AI ile paylaşmadım. :)

import XCTest

class PersonalInfoPage {
let app: XCUIApplication

init(app: XCUIApplication) {
self.app = app
}

var personalInfoHeader: XCUIElement {
return app.staticTexts["Kişisel Bilgiler"]
}

var nameTextField: XCUIElement {
return app.textFields["Ad"]
}

var surnameTextField: XCUIElement {
return app.textFields["Soyad"]
}

var emailTextField: XCUIElement {
return app.textFields["E-posta"]
}

var saveButton: XCUIElement {
return app.buttons["Kaydet"]
}

func verifyPersonalInfoPage() {
XCTAssertTrue(personalInfoHeader.exists, "Kişisel Bilgiler başlığı görünmüyor.")
}
}
import XCTest

class PersonalInfoTests: BaseTest {
var personalInfoPage: PersonalInfoPage!

override func setUpWithError() throws {
try super.setUpWithError()

personalInfoPage = PersonalInfoPage(app: app)
}

func testPersonalInfoPage() throws {
// 1. Hesabım tabına gitmek
let hesabimTab = app.tabBars.buttons["Hesabım"]
hesabimTab.tap()

// 2. Ayarlar menüsünden Kişisel Bilgiler sayfasına gitmek
let settingsButton = app.tables.cells.staticTexts["Ayarlar"]
XCTAssertTrue(settingsButton.exists, "Ayarlar menüsü görünmüyor.")
settingsButton.tap()

let personalInfoButton = app.tables.cells.staticTexts["Kişisel Bilgiler"]
XCTAssertTrue(personalInfoButton.exists, "Kişisel Bilgiler seçeneği görünmüyor.")
personalInfoButton.tap()

// 3. Kişisel Bilgiler sayfasının açıldığını doğrulamak
personalInfoPage.verifyPersonalInfoPage()

// 4. Kişisel bilgileri değiştirmek ve kaydetmek
personalInfoPage.nameTextField.tap()
personalInfoPage.nameTextField.clearAndEnterText(text: "Yeni Ad")

personalInfoPage.surnameTextField.tap()
personalInfoPage.surnameTextField.clearAndEnterText(text: "Yeni Soyad")

personalInfoPage.emailTextField.tap()
personalInfoPage.emailTextField.clearAndEnterText(text: "yenimail@example.com")

personalInfoPage.saveButton.tap()

// 5. Başarılı kayıt mesajını doğrulamak
let successMessage = app.staticTexts["Bilgiler başarıyla güncellendi."]
XCTAssertTrue(successMessage.waitForExistence(timeout: 5), "Başarı mesajı görünmüyor.")
}
}

extension XCUIElement {
func clearAndEnterText(text: String) {
guard let stringValue = self.value as? String else {
XCTFail("Clearing and entering text failed")
return
}

let deleteString = stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined(separator: "")
self.typeText(deleteString)
self.typeText(text)
}
}
import XCTest

class BaseTest: XCTestCase {
var app: XCUIApplication!

override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
}

Kendisinden Giriş Yap sayfasının test otomasyon kodlarını üretmesini , test senaryoları ve ekran görüntüleri vererek üretmesini, hatta IPA dosyasını vererek otomasyon kodları üretmesini istedim. Bu isteklerimin hepsini kodlar üreterek karşıladı. Prompt girişler detaylandıkça yapay zekanın çıktı kalitesi ve derinliği arttı.

Tabii ki, istediğimiz her şeyi ilk seferde bize veremedi ama bizler için büyük kolaylıklar sağlayacağına hiç şüphemiz yok. Yapay zekaya takıldığımız yerlerde, anlamadığımız kod satırlarını sorarak ve hatta kodlarımızı gözden geçirmesi bile isteyebiliriz. Pair programming deki bir pair gibi yardımcı hemde mentor olarak gördük, kaba kodları ona yazdırıp kodlar üzerinden düzenlemeler yaparak ilerledik. Böylece işleri hızlandırıp, bize daha katma değer sağlayacak şeylere daha fazla odaklanabiliyoruz.

Yapay zeka bizleri yüzde yüz mutlu edemeyebilir ama görünen o ki yapay zekayla test otomasyonu yapmak bizler için çok önemli bir rol oynayacaktır ve büyük kolaylıklar sağlayacaktır.

--

--