iOS swift UITest 基础入门(一)

在项目组内做UITest几个月了,输出才是真正的提高嘛,总结了一下,写出来做一个UITest的讲解.

首先说一下目的:UITest,可以模拟人的操作,当然还可以使用第三方用以模拟网络请求,再加上数据库操作等,实现完全的自动化全流程测试(大概是这么个词),在这个过程中可以设定网络返回的数据,设定数据库中的值等等来测试各种case. 下班的时候Command+U一下就可以跑所有的测试类测试case,早上来就可以收割问题了.

下面从建类开始讲.

1,UITest类的创建.

iOS swift UITest 基础入门(一)_第1张图片iOS swift UITest 基础入门(一)_第2张图片

 

如图所示,在工程主文件夹的相应UITest文件夹下添加UITestCaseClass,选择swift建立类.类名在开发的时候,一般以viewController为单位建立相应的test类,类名一般取XXXViewControllerUITests,方便查找和跑相应的测试.

类建好后,样子如下图(注意当前UITest类的target是不是UITest,有些时候建完默认是UnitTest,要手动点回来,否则跑不起来):

iOS swift UITest 基础入门(一)_第3张图片

当前测试类函数的调用顺序讲一下:

每一个testXXX测试函数,都会调用一次setUp()函数,然后运行自身,最后调用tearDown()函数.默认在setUp函数里要预制你的数据(数据库写入等等),调用.launch函数启动APP,在tearDown()函数里清除掉你的数据,关掉APP.

setUp里的.launch()函数是app启动.我一般都删掉这个,自定义一个启动函数放到各个test函数里,这样就可以在启动的时候在自己的测试函数里加载针对每个测试函数自己定义的数据库文件网络返回数据文件等等.另外,我司的项目中,测试数据都写在realm的内存数据库中,所以没有写清除数据的代码.这些先不关注,先来看看重点的testExample函数.

2,测试函数testXXX

testExample函数是苹果举的一个例子.所有测试函数都要类似testXXX,以test开头,这样测试函数才可以跑起来,函数前面才会有那个菱形的小框.很多时候系统反应慢,建完函数build一下就有了.

iOS swift UITest 基础入门(一)_第4张图片

 

把光标放到函数里面,下面的红色就圆点就会变为深红色的可点击状态.点击这个就可以开始录制代码了.

3,录制代码

录制代码是UITest中比较简单获取页面控件和动作代码的方式,app启动,点击,翻页,滑动等等都可以转变成代码,但是在我熟悉了各种语法之后,只在起始阶段用它来录制基本流程代码,然后用我自己熟悉的代码改写.原因就在于录制的代码又臭又长,而且有时候跑不起来,最要命的是各个机种还有差异,一套代码跑不了几个机型.

XCode左上角目标设备选一个模拟器,保持光标在函数内,点击红色小圆点也就是录制按钮,模拟器启动,开始生成代码.下面来看看操作几下之后录制的代码,关键字与正常的swift代码有很大区别:

iOS swift UITest 基础入门(一)_第5张图片

关于代码中的控件获取方式:

1, 如app.buttons[“identifer”] 通过button的title或者设置的identifer获取button,类似的还有app.tables, app.textFields,app.staticTexts等等.有个Xcode工具也能提供一些帮助:

iOS swift UITest 基础入门(一)_第6张图片iOS swift UITest 基础入门(一)_第7张图片

有些设置了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 和 长一点的测试代码样例可以写.

 

 

 

 

你可能感兴趣的:(记录)