美文网首页ios 开发iOS常用
[iOS] UI自动化测试

[iOS] UI自动化测试

作者: 木小易Ying | 来源:发表于2020-09-06 21:05 被阅读0次

    什么是UI自动化测试呢?就是说我们跑一个程序,然后就会看到app跑起来,并且不用我们做操作,它自己会实现各种点击跳转之类的,这样我们就可以把一些标准case转化为代码,每次发包之前跑一次,减少了QA的操作。


    1. XCode 7之前的 UI Automation

    参考:https://www.jianshu.com/p/0e28ae1bd2c2

    这个part不会仔细说,因为现在的Xcode已经没有这个功能了,如果感兴趣可以看上面的文章,大概的操作就是通过instruments里面的Automation,然后输入脚本:

    编写脚本的位置

    2. XCTest框架

    Xcode 8 及以后,苹果将之前的自动化Automation换成了内置的代码target,可以运行起来进行自动化的展示点击之类的。

    可以参考:https://www.jianshu.com/p/10795157fdc0

    其实挺简单的,主要是以下几个步骤:

    • create UI Testing target


      创建target
    • code cases


      写代码
    • run the target (点那个小三角运行)


      run test
    如何写UI测试代码

    不管是Unit Test Class还是 UI Test Class 都继承自XCTestCase。Test Class中编写的测试方法都要以test开头,否则的话不会执行。因此,如果想暂时关闭测试方法,可以再方法名前面加一个Disable前缀,简洁明了,func DISABLE_testAddition() 这样。下图是一个Test Method 实现:

    - (void)testButtonClick {
        XCUIApplication *app = [[XCUIApplication alloc] init];
        [app launch];
        [app.buttons[@"ScrollTestViewController"] tap];
        NSLog(@"ScrollTestViewController tapped");
        
        sleep(10);
    }
    

    如果我们打开Test Class 会看到每个测试类都回重写setUp()tearDown()方法。
    其实所有的test方法是异步执行的,并且在执行之前都会调用setUp()方法,在执行之后都会调用tearDown()方法

    因此有些重复的公有代码,可以放在setUp()tearDown()方法中, 比如启动程序,设置公有的测试模拟数据等。在测试的执行代码运行后,通常会使用XCAssert来断言运行结果XCAssert会很明显的呈现测试的结果,一旦设置的断言条件不通过,就会中断并显示结果,有助于我们的定位问题。


    XCTest框架briefing

    这一部分只是一些简要的东西,具体内容还是要看官方文档。XCTest Framework是 UITest 实现的关键,主要由XCUIApplicationXCUIElementXCUIElementQuery三个类以及XCUIElementAttributesXCUIElementTypeQueryProvider两个协议构成。

    XCUIApplication类继承自XCUIElement类,XCUIElement类遵循XCUIElementAttributesXCUIElementTypeQueryProvider协议,而XCUIElementTypeQueryProvider协议返回的UI元素对象则是XCUIElementQuery类。

    XCUIApplication实现application的launch、 terminal、 active、 state等功能, XCUIElement定义元素的操作事件,XCUIElementTypeQueryProvider协议实现查询所有的UI元素对象,XCUIElementAttributes则表示元素的属性,XCUIElementQuery表示元素的一些功能等等。

    来看一个UI Test的实现,通过XCUIElementTypeQueryProvider获取到UI 元素对象,并且向对象发送事件。

    class CalcUITests: XCTestCase {
    
    let app = XCUIApplication() //获取XCUIApplication对象
         
    override func setUp() {
        super.setUp()
        
        continueAfterFailure = false
        XCUIApplication().launch() //XCUIApplication加载
    }
    
    override func tearDown() {
        super.tearDown()
    }
    
    func testAddition() {
        
         //查询到app的元素,并发送事件
        app.buttons["6"].tap()
        app.buttons["+"].tap()
        app.buttons["2"].tap()
        app.buttons["="].tap()
        
        if let textFieldValue = app.textFields["display"].value as? String {
            XCTAssertTrue(textFieldValue == "8", "Part 1 failed.")
        }
    }
    

    我们能从element上面获取哪些信息呢,其实就是XCUIElementAttributes的内容:

    @protocol XCUIElementAttributes
    
    /*! The accessibility identifier. */
    @property (readonly) NSString *identifier;
    
    /*! The frame of the element in the screen coordinate space. */
    @property (readonly) CGRect frame;
    
    /*! The raw value attribute of the element. Depending on the element, the actual type can vary. */
    @property (readonly, nullable) id value;
    
    /*! The title attribute of the element. */
    @property (readonly, copy) NSString *title;
    
    /*! The label attribute of the element. */
    @property (readonly, copy) NSString *label;
    
    /*! The type of the element. /seealso XCUIElementType. */
    @property (readonly) XCUIElementType elementType;
    
    /*! Whether or not the element is enabled for user interaction. */
    @property (readonly, getter = isEnabled) BOOL enabled;
    
    /*! The horizontal size class of the element. */
    @property (readonly) XCUIUserInterfaceSizeClass horizontalSizeClass;
    
    /*! The vertical size class of the element. */
    @property (readonly) XCUIUserInterfaceSizeClass verticalSizeClass;
    
    /*! The value that is displayed when the element has no value. */
    @property (readonly, nullable) NSString *placeholderValue;
    
    /*! Whether or not the element is selected. */
    @property (readonly, getter = isSelected) BOOL selected;
    
    #if TARGET_OS_TV
    /*! Whether or not the elment has UI focus. */
    @property (readonly) BOOL hasFocus;
    #endif
    
    @end
    

    我们能从app里面拿到哪些东西呢,其实就是XCUIElementTypeQueryProvider里面的内容,例如buttons:

    @protocol XCUIElementTypeQueryProvider
    
    @property (readonly, copy) XCUIElementQuery *touchBars;
    @property (readonly, copy) XCUIElementQuery *groups;
    @property (readonly, copy) XCUIElementQuery *windows;
    @property (readonly, copy) XCUIElementQuery *sheets;
    @property (readonly, copy) XCUIElementQuery *drawers;
    @property (readonly, copy) XCUIElementQuery *alerts;
    @property (readonly, copy) XCUIElementQuery *dialogs;
    @property (readonly, copy) XCUIElementQuery *buttons;
    @property (readonly, copy) XCUIElementQuery *radioButtons;
    @property (readonly, copy) XCUIElementQuery *radioGroups;
    @property (readonly, copy) XCUIElementQuery *checkBoxes;
    @property (readonly, copy) XCUIElementQuery *disclosureTriangles;
    @property (readonly, copy) XCUIElementQuery *popUpButtons;
    @property (readonly, copy) XCUIElementQuery *comboBoxes;
    @property (readonly, copy) XCUIElementQuery *menuButtons;
    @property (readonly, copy) XCUIElementQuery *toolbarButtons;
    @property (readonly, copy) XCUIElementQuery *popovers;
    @property (readonly, copy) XCUIElementQuery *keyboards;
    @property (readonly, copy) XCUIElementQuery *keys;
    @property (readonly, copy) XCUIElementQuery *navigationBars;
    @property (readonly, copy) XCUIElementQuery *tabBars;
    @property (readonly, copy) XCUIElementQuery *tabGroups;
    @property (readonly, copy) XCUIElementQuery *toolbars;
    @property (readonly, copy) XCUIElementQuery *statusBars;
    @property (readonly, copy) XCUIElementQuery *tables;
    @property (readonly, copy) XCUIElementQuery *tableRows;
    @property (readonly, copy) XCUIElementQuery *tableColumns;
    @property (readonly, copy) XCUIElementQuery *outlines;
    @property (readonly, copy) XCUIElementQuery *outlineRows;
    @property (readonly, copy) XCUIElementQuery *disclosedChildRows;
    @property (readonly, copy) XCUIElementQuery *browsers;
    @property (readonly, copy) XCUIElementQuery *collectionViews;
    @property (readonly, copy) XCUIElementQuery *sliders;
    @property (readonly, copy) XCUIElementQuery *pageIndicators;
    @property (readonly, copy) XCUIElementQuery *progressIndicators;
    @property (readonly, copy) XCUIElementQuery *activityIndicators;
    @property (readonly, copy) XCUIElementQuery *segmentedControls;
    @property (readonly, copy) XCUIElementQuery *pickers;
    @property (readonly, copy) XCUIElementQuery *pickerWheels;
    @property (readonly, copy) XCUIElementQuery *switches;
    @property (readonly, copy) XCUIElementQuery *toggles;
    @property (readonly, copy) XCUIElementQuery *links;
    @property (readonly, copy) XCUIElementQuery *images;
    @property (readonly, copy) XCUIElementQuery *icons;
    @property (readonly, copy) XCUIElementQuery *searchFields;
    @property (readonly, copy) XCUIElementQuery *scrollViews;
    @property (readonly, copy) XCUIElementQuery *scrollBars;
    @property (readonly, copy) XCUIElementQuery *staticTexts;
    @property (readonly, copy) XCUIElementQuery *textFields;
    @property (readonly, copy) XCUIElementQuery *secureTextFields;
    @property (readonly, copy) XCUIElementQuery *datePickers;
    @property (readonly, copy) XCUIElementQuery *textViews;
    @property (readonly, copy) XCUIElementQuery *menus;
    @property (readonly, copy) XCUIElementQuery *menuItems;
    @property (readonly, copy) XCUIElementQuery *menuBars;
    @property (readonly, copy) XCUIElementQuery *menuBarItems;
    @property (readonly, copy) XCUIElementQuery *maps;
    @property (readonly, copy) XCUIElementQuery *webViews;
    @property (readonly, copy) XCUIElementQuery *steppers;
    @property (readonly, copy) XCUIElementQuery *incrementArrows;
    @property (readonly, copy) XCUIElementQuery *decrementArrows;
    @property (readonly, copy) XCUIElementQuery *tabs;
    @property (readonly, copy) XCUIElementQuery *timelines;
    @property (readonly, copy) XCUIElementQuery *ratingIndicators;
    @property (readonly, copy) XCUIElementQuery *valueIndicators;
    @property (readonly, copy) XCUIElementQuery *splitGroups;
    @property (readonly, copy) XCUIElementQuery *splitters;
    @property (readonly, copy) XCUIElementQuery *relevanceIndicators;
    @property (readonly, copy) XCUIElementQuery *colorWells;
    @property (readonly, copy) XCUIElementQuery *helpTags;
    @property (readonly, copy) XCUIElementQuery *mattes;
    @property (readonly, copy) XCUIElementQuery *dockItems;
    @property (readonly, copy) XCUIElementQuery *rulers;
    @property (readonly, copy) XCUIElementQuery *rulerMarkers;
    @property (readonly, copy) XCUIElementQuery *grids;
    @property (readonly, copy) XCUIElementQuery *levelIndicators;
    @property (readonly, copy) XCUIElementQuery *cells;
    @property (readonly, copy) XCUIElementQuery *layoutAreas;
    @property (readonly, copy) XCUIElementQuery *layoutItems;
    @property (readonly, copy) XCUIElementQuery *handles;
    @property (readonly, copy) XCUIElementQuery *otherElements;
    @property (readonly, copy) XCUIElementQuery *statusItems;
    
    /*!
     * Returns an element that will use the query for resolution. This changes how the query is resolved
     * at runtime; instead of evaluating against every element in the user interface, `firstMatch` stops
     * the search as soon as a single matching element is found. This can result in significantly faster
     * evaluation if the element is located early in a large view hierarchy but also means that multiple
     * matches will not be detected.
     */
    @property (readonly) XCUIElement *firstMatch;
    
    @end
    

    我们可以对element做的操作可以看XCUIElement,因为比较多所以就不都贴出来啦,看几个叭:

    /*!
     * Moves the cursor over the element.
     */
    - (void)hover;
    
    /*!
     * Sends a click event to a hittable point computed for the element.
     */
    - (void)click;
    
    /*!
     * Sends a double click event to a hittable point computed for the element.
     */
    - (void)doubleClick;
    
    /*!
     * Sends a right click event to a hittable point computed for the element.
     */
    - (void)rightClick;
    

    单元测试和 UI 测试的区别

    对于Unit Test 和 UI Test,两者实现的方式是截然不同的。单元测试的实现方式是保证测试目标类的访问权限,实例化这个类 或者 模拟这个类 (使用类似 OCMock的方式 ),然后在测试方法中调用这个类需测试的方法、功能。

    而对 UI Test,则依靠XCFramework实现。利用XCUIApplication、 XCUIElement 、XCUIElementQuery来获取的App的各个UI对象,同步事件等等,并且将触发事件发送给UI对象。

    Unit Test 会访问目标类的内部方法,而UI Test则不会,只会在外部触发事件。


    集成

    我们编写测试代码的最终目的其实是为了方便测试和修改,这样的话测试自动化即持续集成会带来很多益处。即时发现代码问题、保证主干版本质量、节约测试时间成本等等。因此将Test持续集成到CI工具是很好的做法。Xcode可以很好的和OS X Server结合使用,关联到 Git 版本库之后创建包含 Test 的bot,持续构建 测试 和 部署。

    当然了你还可以使用xcodebuild命令行工具编写自动构建、测试脚本,结合Fastlane 和 Jenkins ,实现自动化测试。


    3. 远程控制测试(电脑控制手机自动跑case但不用xcode)

    这一块主要是通过手机端的Python代码远程遥控手机的自动测试,主要是借用wda框架。

    WebDriverAgent briefing

    可以参考:https://www.jianshu.com/p/b2007f520c77

    首先是这个框架是干啥的?WDA(WebDriverAgent)是Facebook推出的一个开源ios自动化测试框架。(官方链接:https://github.com/facebookarchive/WebDriverAgent

    你可以通过mac的 xcode WebDriverAgent 工程 run起一个wda的程序在手机上,然后手机就成为了一个server,电脑就可以通过端口控制手机跑自动测试(自动测试仍旧是借助于XCTest的框架哒),server也就是手机再通过调用XCTest.framework和调用苹果的API直接在设备上执行命令。

    wda原理
    WebDriverAgent setup

    这部分会分为两个part,在手机上run起wda的工程 & 编写Python程序来调动手机的自动测试

    (1)在手机上run起wda的工程
    • 安装各种wda依赖的库
    ① brew install carthage
    
    ② brew install node
    
    ③ brew install --HEAD libimobiledevice
    
    ④ npm install -g iproxy
    
    • 下载wda源码
    git clone https://github.com/appium/WebDriverAgent
    
    • 初始化
      进入wda源码下载的路径,执行 ./Scripts/bootstrap.sh。

    • 改签名(这里需要用自己的开发者账号)


      改sign
    改runner的bundle id
    • 通过 Product->Test 跑到手机上面


      跑到手机上面
    • 如果成功的话你会在xcode的console里面看到这些:

    2020-09-07 14:01:50.959728+0800 WebDriverAgentRunner-Runner[1387:217902] Built at Sep  7 2020 11:11:06
    2020-09-07 14:01:50.983945+0800 WebDriverAgentRunner-Runner[1387:217902] ServerURLHere->http://10.0.240.98:8100<-ServerURLHere
    2020-09-07 14:01:50.984597+0800 WebDriverAgentRunner-Runner[1387:218027] Using singleton test manager
    
    • 然后连接看一下,通过电脑打开http://localhost:8100/status看到下面这样就对了:
      status

    上面已经实现了在手机上面跑一个wda的程序,下面我们来连接这个手机的server做一些事情。

    • 手机上面的server不能通过外部连接,所以需要用iproxy转换一下,注意这里的端口号其实就是上面命令行打出来的:
    命令行输入:iproxy 8100 8100                        
    输出:Creating listening port 8100 for device port 8100
    waiting for connection
    

    这里的waiting其实是手机在waiting我们的连接

    • 安装python库
    pip install facebook-wda
    
    • 新建个python文件写代码
    import wda
    
    
    def test():
        c = wda.Client('http://localhost:8100')
        print(c.status())
        c.screenshot('screen.png')
        c.session('app bundle id')
        # c.lock()
        # c.unlock()
    
    
    # Press the green button in the gutter to run the script.
    if __name__ == '__main__':
        test()
    

    然后只要你的xcode还在运行wda的runner,你跑起来这个py文件,就会发现自动截屏了~ 但是截屏会存在你的电脑上面:


    截屏文件
    附录:如何识别页面元素(Accessibility Inspector)
    入口

    你选中自己的手机,然后点右侧的小定位按钮,等待它变为蓝色以后,点击手机上面,会发现有绿色的小方块,也就是你选中了哪一个,Accessibility Inspector正在展示的就是哪个元素的信息:


    元素信息

    你就可以通过元素的 title 或者 identifier 拿到元素做操作啦~ 例如:

    s(id="URL").exists # return True or False
    
    # using id or other query conditions
    s(id='URL')
    
    # using className
    s(className="Button")
    
    # using name
    s(name='URL')
    s(nameContains='UR')
    s(nameMatches=".RL")
    
    # using label
    s(label="label")
    s(labelContains="URL")
    
    # using value
    s(value="Enter")
    s(valueContains="RL")
    
    • 然而,有些时候我们点击app中的UI组件的时候,会出现点击的组件无法定位或者整个手机屏幕都变绿的情况,这时候我们可以尝试两种解决方法:
    1. 点击inspector中的左移或者右移按钮,这时候会发现app中绿色的位置在移动,继续点击移动到想要选中的组件即可。

    2. 若使用第一种方法依然无法选中想点击的组件,可能是组件之间的层次关系导致的问题。当前inspector绿色色块所在的UI层级,跟想要点击的UI组件不是同一个层级,所以无论如何移动都无法移动到想要点击的组件。

    • 但我们观察inspector工具界面,可以发现界面下半部分可以显示出UI层次结构,并且我们可以点击箭头进入更下级的UI层次Hierarchy,然后我们继续点击inspector的左右移动按钮,就可以选中目标UI组件

    • 特别说明,有时候已经选中目标UI组件,却发现它的Accessibility属性都是空的,这时候就没办法利用Accessibility属性来定位UI组件了,一个可能的办法是可以让开发同学给目标UI组件加上Accessibility属性。

    相关文章

      网友评论

        本文标题:[iOS] UI自动化测试

        本文链接:https://www.haomeiwen.com/subject/cxrqektx.html