iOS swift UITest 基礎入門(一)

在項目組內做UITest幾個月了,輸出纔是真正的提高嘛,總結了一下,寫出來做一個UITest的講解.

首先說一下目的:UITest,可以模擬人的操作,當然還可以使用第三方用以模擬網絡請求,再加上數據庫操作等,實現完全的自動化全流程測試(大概是這麼個詞),在這個過程中可以設定網絡返回的數據,設定數據庫中的值等等來測試各種case. 下班的時候Command+U一下就可以跑所有的測試類測試case,早上來就可以收割問題了.

下面從建類開始講.

1,UITest類的創建.

 

如圖所示,在工程主文件夾的相應UITest文件夾下添加UITestCaseClass,選擇swift建立類.類名在開發的時候,一般以viewController爲單位建立相應的test類,類名一般取XXXViewControllerUITests,方便查找和跑相應的測試.

類建好後,樣子如下圖(注意當前UITest類的target是不是UITest,有些時候建完默認是UnitTest,要手動點回來,否則跑不起來):

當前測試類函數的調用順序講一下:

每一個testXXX測試函數,都會調用一次setUp()函數,然後運行自身,最後調用tearDown()函數.默認在setUp函數裏要預製你的數據(數據庫寫入等等),調用.launch函數啓動APP,在tearDown()函數裏清除掉你的數據,關掉APP.

setUp裏的.launch()函數是app啓動.我一般都刪掉這個,自定義一個啓動函數放到各個test函數裏,這樣就可以在啓動的時候在自己的測試函數里加載針對每個測試函數自己定義的數據庫文件網絡返回數據文件等等.另外,我司的項目中,測試數據都寫在realm的內存數據庫中,所以沒有寫清除數據的代碼.這些先不關注,先來看看重點的testExample函數.

2,測試函數testXXX

testExample函數是蘋果舉的一個例子.所有測試函數都要類似testXXX,以test開頭,這樣測試函數纔可以跑起來,函數前面纔會有那個菱形的小框.很多時候系統反應慢,建完函數build一下就有了.

 

把光標放到函數裏面,下面的紅色就圓點就會變爲深紅色的可點擊狀態.點擊這個就可以開始錄製代碼了.

3,錄製代碼

錄製代碼是UITest中比較簡單獲取頁面控件和動作代碼的方式,app啓動,點擊,翻頁,滑動等等都可以轉變成代碼,但是在我熟悉了各種語法之後,只在起始階段用它來錄製基本流程代碼,然後用我自己熟悉的代碼改寫.原因就在於錄製的代碼又臭又長,而且有時候跑不起來,最要命的是各個機種還有差異,一套代碼跑不了幾個機型.

XCode左上角目標設備選一個模擬器,保持光標在函數內,點擊紅色小圓點也就是錄製按鈕,模擬器啓動,開始生成代碼.下面來看看操作幾下之後錄製的代碼,關鍵字與正常的swift代碼有很大區別:

關於代碼中的控件獲取方式:

1, 如app.buttons[“identifer”] 通過button的title或者設置的identifer獲取button,類似的還有app.tables, app.textFields,app.staticTexts等等.有個Xcode工具也能提供一些幫助:

有些設置了accessibility identifier的控件直接就用工具確認OK就讀出來了,有些用value也可以讀出來,大多數時候我更喜歡用模糊匹配來獲取控件.

如:let myPredicate = NSPredicate(format: "value like '*1999年*'")
            let wheel = app.pickerWheels.element(matching: myPredicate)

2,app.children(matching:.window).element(boundBy: 0).children(matching: .other) 

//matching後面是控件類型枚舉值,有幾十種,element(boundBy:0)這裏就是選擇第0個window控件.通過這種控件過濾及嵌套查找,可以隨意找到我們想要的大部分控件.注意嵌套太長的話儘量找替代方法,比如用identifier,否則太影響閱讀了. 適合沒有title的button,沒有placeHolder的textField這種控件使用.

點擊程序停止按鈕或者停止錄製按鈕,結束錄製代碼.錄製好的代碼(補上去一點斷言就是最基本的測試代碼了)點擊test函數前面的小菱形即可運行,如運行成功無錯誤菱形會變成綠色,測試失敗會變成紅色.點擊類名前面的菱形運行整個類的測試代碼,command+U運行整個程序的測試代碼.

下面關於語句略做講解:

4,常用語句

UITest函數運行的時候可以看到新生成了一個TestAPP,然後纔是我們的APP啓動.這裏獲取控件都是XCUIElement,可以看到,代碼大意描述了獲取各種element(對應被測試APP內的各種label,button控件)然後做動作(如tap()),整個UITest基本都是類似的套路,還是可以很容易學明白.

下面註解一下常用的代碼的意思:

#1 XCUIApplication() 獲取正在運行的app, XCUIApplication類裏常用的方法

     ##1 launchArguments與launchEnvironment  用來在測試前在自己喜歡的位置加載數據等等.

       例如測試函數加入以下代碼:

func testExampleForLogin() {

        let app = XCUIApplication()
        app.launchArguments = ["TEST"]
        var launchEnvironment = [String : String]()
        launchEnvironment["TEST"] = *文件名*
        launchEnvironment["TEST_FILE"] = *另一個用途的文件名*
        app.launchEnvironment = launchEnvironment
        app.launch()

//以下略

}

AppDelegate加入以下代碼:

           #if DEBUG
        if ProcessInfo.processInfo.arguments.contains("TEST") {
            //某些處理:如測試數據庫初始化,讀取stub數據等等

             readData()
        } else {
            //其他數據庫初始化
        }
        #else
        //正常調用
        #endif

數據加載類:

func readData() {

    //通過這個讀取到測試函數要加載的JSON或者數據庫文件的文件名,然後進行加載,設置stub或者寫入數據庫等等.

      let stubInfoJsonString = ProcessInfo.processInfo.environment["TEST"]

      //以下略

}

  ##2 frame

XCUIApplication也是XCUIElement(一會再介紹這個控件類)的子類,有個frame屬性比較重要,在測試代碼中有時候要獲取屏幕的寬高(比如判斷iphone4或者x),用UIScreen不同的機型都會獲取到同一個寬高,所以不能用UIScreen,XCUIApplication().frame獲取的纔是當前模擬器的寬高.

     #2 等待控件加載函數func wait(for expectations: [XCTestExpectation], timeout seconds: TimeInterval) -> XCTWaiter.Result

用來等待控件加載,比如加載的菊花轉的時候等待遮罩下面一個button可以點擊是靠譜的.(其實alert不用等,類比一下哈).當然也是可以用sleep函數來等待的,但是比較low.代碼review的時候容易被吊起來打.

        let myPredicate = NSPredicate(format: "exists == true")
        let myExpectation = expectation(for: myPredicate,
                                        evaluatedWith: element,
                                        handler: nil)

        let result = XCTWaiter.wait(for: [myExpectation], timeout: time)

封裝一下,結果加個斷言就可以直接用了.

     #3 UITest中的NSObject --- @available(iOS 9.0, *) XCUIElement

主角介紹晚了,測試裏各種元素的父類,前面的9.0標誌着用XCUIElement做UITest要9.0以上啦.所以我們測試的時候以9.0開始,但9.x的版本實在是噁心,各種bug,各種代碼不適配(錄製我一般用iphone8 11.x版本,沒辦法,就是順滑),屏幕類型包含4和5,小屏幕加低版本,動作有時候很怪異.實在要測9.x+4s的時候我一般用9.3版本.9.0似乎根本跑不起來自動測試.大致介紹一下他的成員及函數,都比較簡單.

      ##1 exists   控件是否存在,配合斷言使用

      ##2 waitForExistence 等待出現.可以加timeout,配合斷言使用

      ##3 isHittable 是否可以點擊,配合斷言使用

      ##4 children 返回子元素查詢結果集

例: let textField1 = XCUIApplication().scrollViews.otherElements.containing(.staticText, identifier:"ID").children(matching: .textField).element //一般模擬器自動錄代碼會出現這種長度的代碼,我們寫的時候直接 app.TextFields["ID"]就可以獲取到了.

      ##5 func coordinate(withNormalizedOffset normalizedOffset: CGVector) -> XCUICoordinate 控件中的某個位置座標,其中CGVector是指定位置在控件中的橫豎比例(控件總長橫豎均爲1,算比例),這個函數配合press函數是實現精確滑動動作(某個控件的指定位置滑到另一個控件的指定位置)的必用函數,很簡單也很好用.

      ##6 typeText 文本框輸入指定文字必備方法,可以不調用鍵盤打鍵(調用軟鍵盤打字太慢了...)

      ##7 tap() 點擊,其他還有doubleTap()等等

      ##8 press(forDuration duration: TimeInterval, thenDragTo otherElement: XCUIElement) press函數帶dragTo, 配合coordinate函數,精確滑動,很是舒服.例:

aTableViewCell.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: offset)).press(forDuration: 0.2, thenDragTo:  bTableView.coordinate(withNormalizedOffset: CGVector(dx: 0.8, dy: 0.1)))  //一個cell滑動到另一個cell的位置.

     ##9 swipeUp(),swipeDown(),swipeLeft(),swipeRight() 上下左右滑控件

    #4 XCTAssert 斷言類

Test類如果沒有斷言,基本就失去了一大半的意義,斷言出錯會留下log和標記,我們可以直接找到錯誤的地方然後對程序做對應的修改.沒有斷言或者斷言少的測試case,失敗的時候函數前面顯示個紅色x,但是根本找不到錯在哪裏.斷言用起來很簡單:

XCTAssert(xxxResult == .completed, "LOG用語句")

XCTAssertTrue(app.textFields["0999999"].exists)
XCTAssertFalse(app.buttons[“Login”].isEnabled)

XCTAssertEqual(secureTextField.value as? String, "")  //兩個是否相等

---TO BE CONTINUE---

後半夜了,明天繼續寫,還有其他語句介紹以及網絡mock第三方OHHTTPStubs 和 長一點的測試代碼樣例可以寫.

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章