iOS: Ui Test on Device(s) with Command Line!
จากที่ได้รับหน้าที่เป็น Mobile Dev. ฝั่ง iOS ก็ได้ทำ CI บน Jenkins ทั้ง Unit testing และ Ui Testing ผ่าน Fastlane …. ก็สะดวกดี
แต่มีปัญหาคือหลายครั้งที่เมื่อ run ui test บนเครื่อง jenkins มันไม่เสถียร
พังบ้าง ผ่านบ้าง แล้วแต่ดวง(พูดเล่นนะ) เพราะว่า UiTest ที่มันมากับ fastlane ui-testing เนี่ย โดย default รันบน simulator ที่ใช้ ทรัพยากรของเครื่อง
ซึ่งเมื่อมี jobs อื่น ๆ รันพร้อมกันก็ทำให้เครื่องช้า (ที่ทำงานใช้ Mac mini รัน Ui, Unit Testing ของทั้ง Android และ iOS)
ทางทีมก็เริ่มอยากรัน Ui Testing บน Device เพราะเชื่อว่ามันจะดีกว่าที่รันผ่าน emulator
และมันก็เป็นอย่างนั้นจริง ๆ เพราะโดยปกติ
เวลาที่รัน UI Test จะบน Jenkinsด้วย fastlane(simulator)ใช้เวลา ~20 min และอาจจะพัง ใช้เวลาหาสาเหตุอีก ~10 min ลองรันบนเครื่องตัวเองอีก ~7 min โถ่ววชีวิต
เเต่เมื่อลองรันบน Device กลับใช้เวลา ~8 min แล้ว Tester ก็เห็น screen ของ device ตอน run ด้วย ดี้ดีย์
ขี้เกียจเล่าแล้วมาลองกันเถอะ…
สร้าง project ง่าย ๆ เพื่อลอง
เหมือนทำแอพเปิดปิด อะไรสักอย่าง…
เมื่อเริ่มต้น app
ให้ status เป็น off
และเมื่อกดปุ่ม ON/OFF ให้ status เปลี่ยนไปเป็น on วนไป ….
ส่วน code นั้นอยู่ด้านล่างจย้า
อย่าลืม set scheme Ui Test แล้วก็ set projectให้รันบน Device ได้นะครับ
ผ่านการเพิ่มเครื่องเข้าไปใน provisioning profile และ เซ็ท Apps IDs ให้โปรเจค
ส่วนนี้ค่อนข้างยาว
ViewController.swift
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var lightStatusLabel: UILabel!
@IBOutlet weak var lightSwitcherButton: UIButton!
var isLightOn = false
override func viewDidLoad() {
super.viewDidLoad()
lightStatusLabel.text = "off"
isLightOn = false
lightSwitcherButton.setTitle("ON/OFF", for: .normal)
}
@IBAction func onTouchSwitcher(_ sender: UIButton) {
defer {
isLightOn = !isLightOn
}
if isLightOn {
lightStatusLabel.text = "off"
} else {
lightStatusLabel.text = "on"
}
}
}
SampleUITests.swift
import XCTest
class SampleUITests: XCTestCase {
func testSwitchLightStatus() {
let app = XCUIApplication()
app.launch()
let onOffButton = app.buttons["ON/OFF"]
// เข้ามาให้เจอ status off ก่อนเลย
XCTAssertTrue(app.staticTexts["off"].exists)
onOffButton.tap() // แตะ 1 ครั้งถ้วนเพื่อเปลี่ยน
XCTAssertTrue(app.staticTexts["on"].exists) // ต้องเห็น on
onOffButton.tap() // แตะอีกครั้งก็ควรจะเห็น off เนาะ
XCTAssertTrue(app.staticTexts["off"].exists)
}
}
จากนั้นก็ลองรันโดยใช้ xcode รัน Ui Test บน scheme Ui Tests ประมาณนี้
พอรันผ่าน sim ก็จะประมาณนี้ บนเครื่องก็เช่นกัน..
แต่ถ้ามันอยู่บนเครื่อง jenkins หละ?? เราคงไม่นั่งมาไล่กด xcode เลือก schme กดรันใช่ไหม 😎
ส่วนที่สำคัญที่สุดของ blog นี้ก็คือบรรทัดนี้
เป็นคำสั่งที่จะให้เรารัน ui test บน device โดยไม่ผ่าน Xcode GUI
ซึ่งทำให้เราสร้าง jenkins jobs เพื่อรัน UiTest บน device ได้
xcodebuild
-project Sample.xcodeproj
-scheme "SampleUITests"
-destination 'platform=iOS,id=xxxxxxxaaaaxxxxxaaaaxxxxx'
test
parameter ก็ตามตัวเลย
project -> Path to xcode project file / ถ้าเป็น workspace ก็ ]-workspace xx.xcworkspace
scheme -> Scheme ที่เซ็ทไว้เพื่อรัน UiTests
destination -> ก็บอก target ของ device / id คือ uuid ของเครื่องที่จะรัน UiTest
และมันก็รันหลายเครื่องพร้อมกันได้ โคตรคูล!! สร้างฟาร์มได้เลย หรือจะแยก
คำสั่งเป็นกล่อง ๆ (jobs) บน jenkins ก็ย่อมได้ โดยใส่ destination ไปอีก…
xcodebuild
-project Sample.xcodeproj
-scheme "SampleUITests"
-destination 'platform=iOS,id={uuid device #1}'
-destination 'platform=iOS,id={uuid device #2}'
test
เท่านี้ก็เป็นอันเสร็จสิ้นหวังว่าจะเป็นประโยชน์กับคนที่ทำ UiTest นะครับ 🍻
สรุป
- รัน UiTest บน Device เร็วกว่า emulator ในบางกรณี (ต้องเป็นเครื่องที่เร็วอยู่แล้วด้วยนะ อาจจะต้อง clear เครื่องก่อน) แล้วก็ไม่กินเครื่องที่ไว้ทำ CI
- การรันผ่าน script ทำให้การรันบน Device ง่ายขึ้น และทำหลาย ๆ เครื่องพร้อมกันได้
Thanks: Chokchai Phatharamalai Joe Chavintron