iOS UI自动化测试

2018-02-22  本文已影响280人  loongod

UI testing

找到交互的UI控件,检测UI控件的属性和状态

生成测试报告,包括每步的截图

核心技术包括XCTest 和 Accessibility

XCTest

Xcode's testing framework

Requirements

UI testing depends on new OS features


Getting Started

UI Testing Xcode Targets

UI tests have special requirements

New Xcode target templates

APIs

Three new classes

UI Recording

Recording generates the code

控件和查询

Element Uniqueness

Every XCUIElement is backed by a query
Query must resolve to exactly one match

XCUIElementQuery

API for specifying elements
Queries resolve to collections of accessible elements

How do queries work?

Expressing relationships

Descendants
Children
Containment

Filtering
过滤方式 方式类型
Element type Button,table,menu.etc
Identifiers Accessibility identifier,label,title,etc
Predicates Value,partial matching,etc

Combining Relationships and Filtering

descandantsMatchingType()
let allButtons = app.descendantsMatchingType(.Button)

let allCellsInTable = table.descendantsMatchingType(.Cell)

let allMenuItemsInMenu = menu.descendantsMatchingType(.MenuItem)
So common, wo provide convenience API for each type
let allButtons = app.buttons
let allCellsInTables = table.cells
let allMenuItemsInMenu = menu.menuItems
childrenMatchingType()

Differentiates between any descendant and a direct child relationship

let allButtons = app.buttons //descendantsMatchingType(.Button)

let childButtons = navBar.childrenMatchingType(.Button)
containingType()

Find elements by describing their descendants

屏幕快照 2018-02-08 下午1.33.17.png
let cellQuery = cells.containingType(.StaticText, identifier:"Groceries")

descendantsMatchingType()
childrenMatchingType()
containingType()
这三个方法也可以同样的方式使用
Combining Queries

Queries can be "chained" together
Output of each query is the input of the next query

屏幕快照 2018-02-08 下午1.39.28.png
let labelsInTables = app.tables.staticTest
Getting Elements from Queries
方式 示例
Subscripting table.staticTests["Groceries"]
Index table.staticTests.elementAtIndex(0)
Unique app.navigationBars.element

生成截图

我运行完之后,发现没有图片生成


屏幕快照 2018-02-08 下午2.07.27.png

需要去scheme中的text设置


text设置

使用终端测试

xcodebuild test -workspace UITerminalDemo.xcworkspace 
-scheme UITerminalDemoUITests 
-destination 'platform=iOS Simulator,name=iPhone X,OS=11.2' 

下面是destination的可用值:
(也是可以用真机的,下面的第一条就是我自己的手机,需要连接上)

Available destinations for the "UITerminalDemoUITests" scheme:
        { platform:iOS, id:86c38f55392f78e1c14ee5b1e5e547492075df20, name:许龙的 iPhone }
        { platform:iOS Simulator, id:E5186B36-BD50-412D-8AD2-E9A1E1F3AB9C, OS:10.3.1, name:iPad (5th generation) }
        { platform:iOS Simulator, id:9AE98D1F-49F6-4D56-BB17-B5D474941C5D, OS:11.2, name:iPad (5th generation) }
        { platform:iOS Simulator, id:3293AA96-0573-4316-8FC5-7B14559F3E20, OS:10.3.1, name:iPad Air }
        { platform:iOS Simulator, id:44E080B6-B931-42BF-988D-E55345DBF8CB, OS:11.2, name:iPad Air }
        { platform:iOS Simulator, id:288C496A-3AF2-413E-B816-71BE66AE8124, OS:10.3.1, name:iPad Air 2 }
        { platform:iOS Simulator, id:7420C15C-05CC-4C55-AA38-6A5D5E0CCF3B, OS:11.2, name:iPad Air 2 }
        { platform:iOS Simulator, id:21C6851F-2F5E-4F8F-BCEF-51878F70986C, OS:10.3.1, name:iPad Pro (9.7 inch) }
        { platform:iOS Simulator, id:8C328212-6A92-4F42-9DBD-DBB1E040EE08, OS:11.2, name:iPad Pro (9.7-inch) }
        { platform:iOS Simulator, id:2F7428EE-C4AE-4D31-A273-0AE1F88C95E6, OS:10.3.1, name:iPad Pro (10.5-inch) }
        { platform:iOS Simulator, id:D53E887B-2704-4919-9E8E-10E7B0B69DFA, OS:11.2, name:iPad Pro (10.5-inch) }
        { platform:iOS Simulator, id:FC2CCFC2-E298-4334-B66D-9D1E9E3F98E2, OS:10.3.1, name:iPad Pro (12.9 inch) }
        { platform:iOS Simulator, id:B6990DA0-DDA0-4999-AD73-E85D14D1A730, OS:11.2, name:iPad Pro (12.9-inch) }
        { platform:iOS Simulator, id:8AD3E839-1319-4E34-A619-4A1F5C60E202, OS:10.3.1, name:iPad Pro (12.9-inch) (2nd generation) }
        { platform:iOS Simulator, id:82AEFE8B-2DCD-499F-A743-46681C38049A, OS:11.2, name:iPad Pro (12.9-inch) (2nd generation) }
        { platform:iOS Simulator, id:C64ED8D5-F709-4E8F-94FC-B4DB97F906F4, OS:10.3.1, name:iPhone 5 }
        { platform:iOS Simulator, id:8DC1550F-7BD0-443E-8895-9DC074217A60, OS:10.3.1, name:iPhone 5s }
        { platform:iOS Simulator, id:C65A270A-653B-4CC4-AADC-D683D0FEB23A, OS:11.2, name:iPhone 5s }
        { platform:iOS Simulator, id:52DBB418-E842-445A-AB95-398D2D4404CF, OS:10.3.1, name:iPhone 6 }
        { platform:iOS Simulator, id:E3E52BB3-51EA-4E47-A72E-49D281BB8F04, OS:11.2, name:iPhone 6 }
        { platform:iOS Simulator, id:8BEDA513-7394-43BF-8A92-56C47704B5EC, OS:10.3.1, name:iPhone 6 Plus }
        { platform:iOS Simulator, id:45D79B21-EE8D-4C6C-8C68-744D8AB388F5, OS:11.2, name:iPhone 6 Plus }
        { platform:iOS Simulator, id:B1B9D218-C692-4C56-8A6A-E73B81885DB4, OS:10.3.1, name:iPhone 6s }
        { platform:iOS Simulator, id:DBC72166-AC98-4E41-A590-DD76F47EB4BF, OS:11.2, name:iPhone 6s }
        { platform:iOS Simulator, id:21B5972B-5377-431F-8445-0CD00BC66B77, OS:10.3.1, name:iPhone 6s Plus }
        { platform:iOS Simulator, id:FDA53200-DBBD-498A-BB58-2B05B17BD785, OS:11.2, name:iPhone 6s Plus }
        { platform:iOS Simulator, id:33122DBD-44EC-40E1-BE5E-85139F0C5B0F, OS:10.3.1, name:iPhone 7 }
        { platform:iOS Simulator, id:6C2EDE5A-4C66-48C6-844F-9A54873904D4, OS:11.2, name:iPhone 7 }
        { platform:iOS Simulator, id:35112E51-2376-4E21-B49D-34A69FBC8DB7, OS:10.3.1, name:iPhone 7 Plus }
        { platform:iOS Simulator, id:44EB9D0D-9D53-4D34-BEAD-7E8DE3162D2D, OS:11.2, name:iPhone 7 Plus }
        { platform:iOS Simulator, id:09E387B5-C0E6-43D9-9BA8-75E3785280C0, OS:11.2, name:iPhone 8 }
        { platform:iOS Simulator, id:F85B7C01-EB2D-4C9E-98C0-CF466941E6E0, OS:11.2, name:iPhone 8 Plus }
        { platform:iOS Simulator, id:E76E917B-DAFB-4849-BC6E-A2AD8CEDE025, OS:10.3.1, name:iPhone SE }
        { platform:iOS Simulator, id:C3F35392-D7C4-44AF-A9E4-4A79E07B4213, OS:11.2, name:iPhone SE }
        { platform:iOS Simulator, id:2B7D423E-D828-4F13-97F3-A2FE4831BF8D, OS:11.2, name:iPhone X }

    Ineligible destinations for the "UITerminalDemoUITests" scheme:
        { platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Generic iOS Device }
        { platform:iOS Simulator, id:dvtdevice-DVTiOSDeviceSimulatorPlaceholder-iphonesimulator:placeholder, name:Generic iOS Simulator Device }

如果想一次测试多个设备怎么办?
可以使用链式语法指定多个-destination

如果项目使用了Cocoapods的话,也就是打开项目使用的是.workspace的话,需要用 -workspace,如果不是的话需要用-project

xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp
-destination 'platform=OS X,arch=x86_64'
-destination 'platform=iOS,name=Development iPod touch'
-destination 'platform=Simulator,name=iPhone,OS=9.0'

如果测试失败,则会返回一个非零的Code码。如果想了解更多的·xcodebuild·命令信息,可以在终端中使用man xcodebuild

生成截图

无需额外的工作,只需添加derivedDataPath选项,记得在scheme配置中不要勾选Delete when each test succeeds

xcodebuild test -workspace UITerminalDemo.xcworkspace 
-scheme UITerminalDemoUITests 
-destination 'platform=iOS Simulator,name=iPhone X,OS=11.2' 
-derivedDataPath './test' 

截图路径:./test/Logs/Test/Attachments/

截图.png

关于查询

导航栏标题的查询

最开始我一直找不到导航栏的标题的Label,最后发现是查询条件的问题,title的类型不是XCUIElementTypeStaticText,而是XCUIElementTypeOther,我是先通过下面的代码找到的

    XCUIApplication *app = [[XCUIApplication alloc] init];
    NSInteger navigationBarCount = app.navigationBars.count;
    NSLog(@"%ld",navigationBarCount);
    XCUIElement *navigationBar = [app.navigationBars elementBoundByIndex:0];
    XCUIElementQuery *navTitleLabels = [navigationBar descendantsMatchingType:XCUIElementTypeAny];
    NSLog(@"navlabels:%ld",navTitleLabels.count);
    XCUIElement *any = [navTitleLabels elementBoundByIndex:0];
    NSLog(@"description: %@", any.debugDescription);
 description: Attributes: Other, 0x60c0001993d0, traits: 8590000128, {{170.3, 55.7}, {34.7, 20.3}}, label: '首页'

可以发现Attributes是Other,下面直接使用XCUIElementTypeOther类型进行查找

    XCUIApplication *app = [[XCUIApplication alloc] init];
    NSInteger navigationBarCount = app.navigationBars.count;
    NSLog(@"%ld",navigationBarCount);
    XCUIElement *navigationBar = [app.navigationBars elementBoundByIndex:0];
    XCUIElementQuery *navTitleLabels = [navigationBar descendantsMatchingType:XCUIElementTypeOther];
    NSLog(@"navlabels:%ld",navTitleLabels.count);
    XCUIElement *other = [navTitleLabels elementBoundByIndex:0];
    NSLog(@"description: %@", other.debugDescription);

打印信息是一样的

 description: Attributes: Other, 0x60c0001993d0, traits: 8590000128, {{170.3, 55.7}, {34.7, 20.3}}, label: '首页'

所以:要找到NavigationBar的标题(系统的)查询类型是XCUIElementTypeOther。


关于检测

XCTFail(...)

生成一个无条件的错误,参数...是输出的提示文字(后面类似)。

/*!
 * @function XCTFail(...)
 * Generates a failure unconditionally.
 * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
*/
#define XCTFail(...) \
    _XCTPrimitiveFail(self, __VA_ARGS__)
XCTAssert(expression, ...)

当参数expression是false的时候生成一个错误,参数...同上。

/*!
 * @define XCTAssert(expression, ...)
 * Generates a failure when ((\a expression) == false).
 * @param expression An expression of boolean type.
 * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
*/
#define XCTAssert(expression, ...) \
    _XCTPrimitiveAssertTrue(self, expression, @#expression, __VA_ARGS__)

通过按住Command+Control,然后点击宏定义,跳进宏定义声明的地方,可查看所有的XCTAssert断言的定义声明。如下

/*!
 * @define XCTAssertNil(expression, ...)
 * Generates a failure when ((\a expression) != nil).
 * @param expression An expression of id type.
 * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
*/
#define XCTAssertNil(expression, ...) \
    _XCTPrimitiveAssertNil(self, expression, @#expression, __VA_ARGS__)

/*!
 * @define XCTAssertNotNil(expression, ...)
 * Generates a failure when ((\a expression) == nil).
 * @param expression An expression of id type.
 * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
*/
#define XCTAssertNotNil(expression, ...) \
    _XCTPrimitiveAssertNotNil(self, expression, @#expression, __VA_ARGS__)

/*!
 * @define XCTAssert(expression, ...)
 * Generates a failure when ((\a expression) == false).
 * @param expression An expression of boolean type.
 * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
*/
#define XCTAssert(expression, ...) \
    _XCTPrimitiveAssertTrue(self, expression, @#expression, __VA_ARGS__)
使用断言

比如我要判断当前页面,可以通过导航标题来判断

    NSInteger navigationBarCount = app.navigationBars.count;
    XCUIElement *navigationBar = [app.navigationBars elementBoundByIndex:0];
    XCUIElementQuery *navTitleLabels = [navigationBar descendantsMatchingType:XCUIElementTypeOther];
    XCUIElement *otherNavTitle = [navTitleLabels elementBoundByIndex:0];
    //判断导航标题是不是详情页
    XCTAssert([otherNavTitle.label isEqualToString:@"详情页"]);

关于测试用例

在test.m中默认有一个testExample,如果你想新写一个测试方法,注意方法名需要用text开头。这样才会在方法的左边出现单个方法测试的可点击按钮,如果写完方法没出现,command+U运行下即可。

text.png

点击@implementation UITests左边的运行所有测试用例或者command+u。
发现Xcode运行测试用例是按字母排序的。如果想要按固定顺序执行测试用例,可以在test后追加数字来标记顺序,比如:

- (void)test00TabPage {
  //your test0
}

- (void)test01TabPage {
  //your test1
}

- (void)test02TabPage {
  //your test2
}

参考:

WWDC15 UI Testing in Xcode PDF

Testing with Xcode

上一篇下一篇

猜你喜欢

热点阅读