Unit Test 和 UI Test
[TOC]
Unit Test
单元测试:从字面上的意思理解就是,将某
一部分
的代码拆成一块一块
的代码进行测试,每一块代码就是一个独立的单元
。一般用于测试某一块代码的逻辑是否正确。
Unit Test文件
在每个工程创建的时候,都会有3个勾选项供你勾选,其中两个默认选中的
Include Unit Tests
和Include UI Tests
,勾选后就会在工程文件中生成两个文件夹ProjectNameTests
和ProjectNameUITests
,ProjectNameTests
就是用于编写这个工程的单元测试的地方,而ProjectNameUITests
就是用于编写UI 自动化测试的地方。也可以通过file -> New -> Target -> iOS -> 搜索test
,添加iOS Unit Testing Bundle
和iOS UI Testing Bundle
添加。我们先来看一下Unit Test。我们可以在
ProjectNameTests
分组中创建对应功能模块的测试文件,系统会自动创建几个方法:
1 | //所有测试用例运行之前都会运行的方法,在该测试文件中的各个测试单元都会用到的一些实体类,都可以在该方法中init |
主要用途:
- 当修改某个公共类的时候,为防止对其他地方造成影响,需要验证一下其他地方是否正确,可以使用单元测试验证,而不需要一步一步的去实现复杂的操作。
- 在开发的时候,有时候需要反复调试接口返回的数据情况或者验证某段代码的正确性,可能要一遍一遍的运行,而每次运行后都需要点击到当前页面,这样经常需要重复复杂的操作,而
Unit Test
则可以省略那些复杂操作。
异步操作
Unit Test的每一个测试单元都可以看做是一个串行线程的程序,当你的测试单元涉及到
异步
的时候,就需要用到XCTestExpectation
,它表示你的一个“期望”,在规定的时间内,当你的“期望”完成时,才会结束这个测试单元。实例如下:
1 | XCTestExpectation *expectation = [self expectationWithDescription:@"网络请求"]; |
- 常用的断言测试语法
1 | XCTFail(format…) //生成一个失败的测试; |
Unit Test注意事项
Unit Test是一个target,所以在Unit Test中导入类的时候,如果涉及到prefix header中的类或者通过cocoaPods导入的三方库的时候,需要在对应的target中需要设置prefix header path和在Podfile文件中添加对应的target。
UI Test
UI自动化测试:通过计算机语言模拟用户操作。在iOS 3.0
的时候,苹果引入了UI Accessibility
,用来辅助身体不方便的人来使用app,与VoiceOver
相互配合,用来阅读被UI Accessibility
分类和标记的UI
元素操作app。而UI Test就是基于UI Accessibility
的,所以我们在写UI Test时,其实就是通过对应的API来查询
到UI上面的元素
,并模拟点击等事件。为了方便开发者编写测试代码,xCode
有一个功能叫录屏,当你开始录制的时候,他会将你的一系列操作自动生成代码,但是要想后面正常跑起来,还需要自己去修改,所以还是需要你去了解UI Test相应的API。
在UI Test中我们使用到的类主要有XCUIApplication
,XCUIElementQuery
,XCUIElement
。
XCUIApplication
继承自XCUElement,类似于AppDelegate,管理应用程序的生命周期,在写测试代码的时候,可以说最重要的一句代码就是
XCUIApplication *app = [[XCUIApplication alloc] init];
,后续获取界面的元素都需要通过app
来获取。属性:
- launchArguments:将在启动时传递给应用程序的参数。
- launchEnvironment:将在启动时传递给应用程序的环境变量。
- state:只读。应用程序的状态。
方法:
- -launch:启动程序。
- -terminate:终止程序。
- -activate:激活应用程序。
- -(BOOL)waitForState:(XCUIApplicationState)state timeout:(NSTimeInterval)timeout:等待应用程序进入某种状态,几秒钟后失效,如果应用程序当前处于指定状态或转换到所需状态,则返回YES,继续执行下面的代码。
创建UI Test文件,系统自动生成的方法和Unit Test是差不多的,添加测试方法的规则也一样,test
开头,不能传参
。
XCUIElementQuery
查询元素,可以说是所有UI元素的一个容器。
- 属性:
- element:当
query
只有一个元素的时候,query
就是element
,可以执行点击
等操作。 - count:
query
中元素的数量。 - allElementsBoundByAccessibilityElement:得到
query
中所有的XCUIElement
元素。 - allElementsBoundByIndex:得到
query
中所有的XCUIElement
元素。 - debugDescription:提供有关查询的调试信息。
- element:当
方法:
-elementAtIndex:通过下标来获取对应的元素,返回一个XCUIElement
,在iOS9
被弃用,可以使用-elementBoundByIndex。- -elementBoundByIndex:通过下标来获取
query
中的元素,返回一个XCUIElement
。 - -elementMatchingPredicate:通过匹配
NSPredicate
(如:placeholderValue == @"Type in number"
)来获取元素,返回一个XCUIElement
。 - -elementMatchingType:identifier:通过类型(如:
XCUIElementTypeTextField
)和id(可以是UIButton
的title
)来获取一个元素,返回一个XCUIElement
。 - -objectForKeyedSubscript:返回一个由键指定的标识符的子代元素,返回一个
XCUIElement
。??? - -descendantsMatchingType:传入一个元素类型,返回一个新的
XCUIElementQuery
,查询对象为当前控件的子子孙孙元素。 - -childrenMatchingType:传入一个元素类型,返回一个新的
XCUIElementQuery
,查询对象为当前控件的子元素。 - -matchingPredicate:传入
NSPredicate
,过滤元素,得到对应的XCUIElementQuery
。 - -matchingType:identifier:通过类型和id来获取一个新的
XCUIElementQuery
。 - -matchingIdentifier:通过id得到一个新的
XCUIElementQuery
。 - -containingPredicate:通过
NSPredicate
,从子节点中找到包含该条件的XCUIElementQuery
对象。 -containingType:identifier:通过类型和id从子节点中找到包含该条件的
XCUIElementQuery
对象。事例:
1 |
XCUIElement
- XCTest.framework中代表所有UI控件的抽象类,通过这个类来实现
点击
,滑动
等操作。 - 属性:
- exists:判断元素是否存在。
- hittable:确定是否可以为元素计算生命值。
- debugDescription:提供有关该元素的调试信息。
方法:
- -waitForExistenceWithTimeout:等待元素的exist属性为真所需的指定时间量,如果超时过期而元素不存在,则返回false。
- -descendantsMatchingType:返回与指定类型匹配的元素的所有后代的查询。
- -childrenMatchingType:返回与指定类型匹配的元素的所有直接子节点的查询。
- -coordinateWithNormalizedOffset:创建并返回带有标准化偏移量的新坐标。
- -typeText:输入文本。
- -tap:点击。
- -doubleTap:双击。
- -twoFingerTap:双指点击。
- -tapWithNumberOfTaps:numberOfTouches使用一个或多个触点发送一个或多个轻击。
- -pressForDuration:长按指定时间。
- -pressForDuration:thenDragToElement:拖动到指定元素
- -swipeUp:上滑。
- -swipeDown:下滑。
- -swipeLeft:左滑。
- -swipeRight:右滑。
- -pinchWithScale:velocity:捏合手势。
- -rotate:withVelocity:旋转手势。
- -adjustToPickerWheelValue:修改
pickerView
的选择值
事例:
1 |
坑
- 在Unit Test中,只需要注意一下
异步操作
。 - 在UI Test中,第一次写测试代码,就需要注意的很多。
- 通过录制自动生成的代码中,中文会转成
Unicode
,需要手动修改。 - UI Test运行时,其实就是自动操作app,而有的界面是需要等待网络请求结束后才刷新或者添加控件的,但是测试代码却不会真实的等待请求结束,所以当去
app
上查询
某个元素
并点击
的时候,会导致测试代码崩溃。这个时候,就需要我们去等待页面绘制或者控件添加出来后才执行点击等操作。解决方法见下面代码块一。 - 在录屏过程中,点击
cell
时,报错:Timestamped Event Matchng Error: Failed To Find Matching Element
,开始的时候一脸懵逼,后来一次不小心点到一个UILabel
发现居然又可以了,后来查阅资料,发现UI
控件有一个属性isAccessibilityElement
,这个属性就是用来表示该控件是否可以被查询
。而UIView
的为NO
,所以无法找到Element
。需要点击cell
上的UILabel
等可以被访问的控件,或者获取UITableView
上所有cell
,然后通过下标去获取cell
元素,然后实现点击
操作。解决方法见下面代码块二。 - 在点击一个别人封装的控件的时候,发现也是无法获取
Element
,然后点击进去看那个控件,发现是继承自UIButton
,因为UIButton
的isAccessibilityElement
默认为YES
,并且没有设置title
或者image
,所以,导致无法查询
到该元素
,和该控件上其他的元素
。这个时候就需要在封装的时候,再其初始化方法中给该控件设置一个accessibilityIdentifier
,并且将isAccessibilityElement
设置为NO
。 - 通过
XCUIElement
的swipeUp
方法滑动,无法滑到指定位置。解决方法见下面代码块三。 - 有的界面,在请求到数据之前,就会将界面布局并显示出来,然后根据请求到的数据,隐藏获取添加其他控件,这个时候,如果在网络请求结束之前去
查询
某个元素
,并tap
,测试程序会崩溃。你看控制台的打印消息,就会告诉你元素偏移
了。其实就是因为网络请求前后元素
位置发生了变化。所以这个时候,你需要等待网络请求结束再去查询
这个元素
。(我这里是通过网络请求是的菊花
来判断网络请求是否结束)。
- 通过录制自动生成的代码中,中文会转成
1 | /** |
1 | /** |
1 | /** |
以上就是我在开发中遇到的一些坑,在遇到这些坑的时候,一脸懵逼,也在网上找了很久,就是没有找到解决方法,这些坑基本都是在毫无办法的时候,然后不小心点击到某个元素
,获取在某种不同的数据情况下想到的原因。希望可以帮到大家。