KIF的全称是Keep it functional。它是一个建立在XCTest的UI测试框架,通过accessibility来定位具体的控件,再利用私有的API来操作UI。由于是建立在XCTest上的,所以你可以完美的借助XCode的测试相关工具(包括命令行脚本)。
特点:
- 最小化迂回时间
继承KIFTestCase,测试代码都是使用OC编写,最大程度减少了中间层。 - 配置简单
直接集成到XCode上,不需要安装多余的包。 - 像用户一样测试
测试代码模仿用户操作,代码很简单 - 自动集成XCode 5以上的测试工具
在XCode上使用就像使用苹果原生的测试框架一样,支持XCode的各种测试工具。
按照KIF的的官方文档进行导入KIF,接下来以官方的demo为例,进行KIF的相关操作的讲解。
目录
- 使用描述
- Tapping
- Show/Hide
- Gesture
- TableView
- Picker
- ModalView
- CollectionView
- ScrollView
- Landscape
- WebView
- Background
- PullToRefesh
- CascadingFailure
- 补充Tips
使用描述
使用KIF主要有两个核心类:
- KIFTestCase XCTestCase的子类
- KIFUITestActor 控制UI,常见的三种是:点击一个View,向一个View输入内容,等待一个View的出现
KIF利用Accessibility来找元素.tapViewWithAccessibilityLabel
这也许是最常被用到的测试动作方法。正如其名称所显示的,它可以在给定的辅助标签模拟在视图上的触击。在大多数情况下,辅助标签和可视的文本标签(例如按钮组件)是配套的。否则你就需要手动设置辅助标签.
一些控件,诸如 UISwitch,更加复杂,需要比简单的触击更复杂的步骤来触发。KIF 提供了一个特殊的 setOn:forSwitchWithAccessibilityLabel: 方法来改变一个切换的状态.
导入KIF框架后,在项目的Tests(如果还没有Tests文件夹,可以先创建Tests)声明一个测试类,必须以Tests结尾(如LoginTests),继承自KIFTestCase。为了让程序在运行每个测试用例时能保证连贯性,在每个类中都声明两个方法beforeEach
与afterEach
保证测试过程中可以进入想要执行的测试页面与退出测试页面。
///进入指定页面
- (void)beforeEach{
[tester tapViewWithAccessibilityLabel:@"Tapping"];
}
///退出指定页面
- (void)afterEach{
[tester tapViewWithAccessibilityLabel:@"Test Suite" traits:UIAccessibilityTraitButton];
}
Tapping
点击控制器上标记为TapViewController Inner ScrollView的视图上的元素
- (void)testTappingViewFromSpecificView{
UIView *scrollView = [tester waitForViewWithAccessibilityIdentifier:@"TapViewController Inner ScrollView"];
UIView *buttonView;
UIAccessibilityElement *element;
[tester waitForAccessibilityElement:&element view:&buttonView withIdentifier:@"Inner Button" fromRootView:scrollView tappable:YES];
if (buttonView != NULL) {
[tester tapAccessibilityElement:element inView:buttonView];
}
}
长按操作
- (void)testLongPressingViewViewWithTraits{
[tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:2];
[tester tapViewWithAccessibilityLabel:@"Select All"];
}
切换按钮状态
- (void)testTogglingASwitch
{
[tester waitForViewWithAccessibilityLabel:@"Happy" value:@"1" traits:UIAccessibilityTraitNone];
[tester setOn:NO forSwitchWithAccessibilityLabel:@"Happy"];
[tester waitForViewWithAccessibilityLabel:@"Happy" value:@"0" traits:UIAccessibilityTraitNone];
[tester setOn:YES forSwitchWithAccessibilityLabel:@"Happy"];
[tester waitForViewWithAccessibilityLabel:@"Happy" value:@"1" traits:UIAccessibilityTraitNone];
}
滑动状态条
- (void)testMovingASlider{
[tester waitForTimeInterval:1];
[tester setValue:3 forSliderWithAccessibilityLabel:@"Slider"];
[tester waitForViewWithAccessibilityLabel:@"Slider" value:@"3" traits:UIAccessibilityTraitNone];
[tester setValue:0 forSliderWithAccessibilityLabel:@"Slider"];
[tester waitForViewWithAccessibilityLabel:@"Slider" value:@"0" traits:UIAccessibilityTraitNone];
[tester setValue:5 forSliderWithAccessibilityLabel:@"Slider"];
[tester waitForViewWithAccessibilityLabel:@"Slider" value:@"5" traits:UIAccessibilityTraitNone];
}
选取系统的图片
- (void)testPickingAPhoto{
[tester tapViewWithAccessibilityLabel:@"Photos"];
[tester acknowledgeSystemAlert];
[tester waitForTimeInterval:0.5f]; // Wait for view to stabilize
[tester choosePhotoInAlbum:@"Camera Roll" atRow:1 column:2];
[tester waitForViewWithAccessibilityLabel:@"UIImage"];
}
Show/Hide
一直寻找区域内标记为B的值为BB的可点击的按钮
- (void)testWaitingForViewWithValue{
NSLog(@"testWaitingForViewWithValue");
[tester waitForTappableViewWithAccessibilityLabel:@"B" value:@"BB" traits:UIAccessibilityTraitButton];
}
选择视图内可点击切换状态的按钮
- (void)testTappingOnlyIfNotSelected{
[tester tapViewIfNotSelected:@"A"];
[tester waitForViewWithAccessibilityLabel:@"A" traits:UIAccessibilityTraitSelected];
// This should not deselect the element.
[tester tapViewIfNotSelected:@"A"];
[tester waitForViewWithAccessibilityLabel:@"A" traits:UIAccessibilityTraitSelected];
}
- (void)tapViewIfNotSelected:(NSString *)label{
UIAccessibilityElement *element;
UIView *view;
[self waitForAccessibilityElement:&element view:&view withLabel:label value:nil traits:UIAccessibilityTraitNone tappable:YES];
if ((element.accessibilityTraits & UIAccessibilityTraitSelected) == UIAccessibilityTraitNone) {
[self tapAccessibilityElement:element inView:view];
}
}
Gesture
手势操作
滑动
四个方向都可操作
typedef NS_ENUM(NSUInteger, KIFSwipeDirection) {
KIFSwipeDirectionRight,
KIFSwipeDirectionLeft,
KIFSwipeDirectionUp,
KIFSwipeDirectionDown
};
示例
- (void)testSwipingRight{
[tester swipeViewWithAccessibilityLabel:@"Swipe Me" inDirection:KIFSwipeDirectionRight];
[tester waitForViewWithAccessibilityLabel:@"Right"];
}
点击
- (void)testPanningRight{
NSString* regexPattern = kPanRightRegex;
NSPredicate *resultTestPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regexPattern];
NSPredicate *noVelocityPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", KPanNoVelocityValue];
UIView* velocityResultView = [tester waitForViewWithAccessibilityLabel:kVelocityValueLabelAccessibilityString];
XCTAssertTrue([velocityResultView isKindOfClass:[UILabel class]], @"Found view is not a UILabel instance!");
UILabel* velocityLabel = (UILabel*)velocityResultView;
UIView* panLabel = [tester waitForTappableViewWithAccessibilityLabel:kPanMeAccessibilityString];
CGPoint centerInView = CGPointMake(panLabel.frame.size.width / 2.0, panLabel.frame.size.height / 2.0);
[panLabel dragFromPoint:centerInView toPoint:CGPointMake(centerInView.x + 30, centerInView.y)];
XCTAssertFalse([noVelocityPredicate evaluateWithObject:velocityLabel.text], @"No valocity value found!");
XCTAssertTrue([resultTestPredicate evaluateWithObject:velocityLabel.text], @"The result doesn`t match the %@ regex pattern", regexPattern);
}
拖拽
- (void)testScrolling{
// Needs to be offset from the edge to prevent the navigation controller's interactivePopGestureRecognizer from triggering
[tester scrollViewWithAccessibilityIdentifier:@"Scroll View" byFractionOfSizeHorizontal:-0.80 vertical:-0.80];
[tester waitForTappableViewWithAccessibilityLabel:@"Bottom Right"];
[tester scrollViewWithAccessibilityIdentifier:@"Scroll View" byFractionOfSizeHorizontal:0.80 vertical:0.80];
[tester waitForTappableViewWithAccessibilityLabel:@"Top Left"];
}
TableView
tableView的手势滑动
[tester swipeRowAtIndexPath:firstCellPath inTableView:tableView inDirection:KIFSwipeDirectionLeft];
点击tableview上指定的某个cell
- (void)testTappingRows{
[tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:2] inTableViewWithAccessibilityIdentifier:@"TableView Tests Table"];
[tester waitForViewWithAccessibilityLabel:@"Last Cell" traits:UIAccessibilityTraitSelected];
[tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] inTableViewWithAccessibilityIdentifier:@"TableView Tests Table"];
[tester waitForViewWithAccessibilityLabel:@"First Cell" traits:UIAccessibilityTraitSelected];
}
移动
- (void)testMoveRowUpUsingNegativeRowIndexes{
[tester moveRowAtIndexPath:[NSIndexPath indexPathForRow:-1 inSection:1]
toIndexPath:[NSIndexPath indexPathForRow:-3 inSection:1] inTableViewWithAccessibilityIdentifier:@"TableView Tests Table"];
}
删除
///删除第一个cell
- (void)testSwipingRows {
UITableView *tableView;
[tester waitForAccessibilityElement:NULL view:&tableView withIdentifier:@"TableView Tests Table" tappable:NO];
[tester waitForAnimationsToFinish];
// First row
NSIndexPath *firstCellPath = [NSIndexPath indexPathForRow:0 inSection:0];
[tester swipeRowAtIndexPath:firstCellPath inTableView:tableView inDirection:KIFSwipeDirectionLeft];
[tester waitForDeleteStateForCellAtIndexPath:firstCellPath inTableView:tableView];
[tester tapViewWithAccessibilityLabel:@"Delete"];
__KIFAssertEqualObjects([tester waitForCellAtIndexPath:firstCellPath inTableViewWithAccessibilityIdentifier:@"TableView Tests Table"].textLabel.text, @"Deleted", @"");
}
设置Button状态
- (void)testTogglingSwitch{
[tester setOn:NO forSwitchWithAccessibilityLabel:@"Table View Switch"];
[tester setOn:YES forSwitchWithAccessibilityLabel:@"Table View Switch"];
}
检查元素是否在视图上 当元素在视图上消失时 执行操作
- (void)testButtonAbsentAfterRemoveFromSuperview{
UIView *view = [tester waitForViewWithAccessibilityLabel:@"Button"];
[view removeFromSuperview];
[tester waitForAbsenceOfViewWithAccessibilityLabel:@"Button"];
}
Picker
时间选择器
- (void)testSelectingDateInPast{
[tester tapViewWithAccessibilityLabel:@"Date Selection"];
NSArray *date = @[@"June", @"17", @"1965"];
// If the UIDatePicker LocaleIdentifier would be de_DE then the date to set
// would look like this: NSArray *date = @[@"17.", @"Juni", @"1965"
[tester selectDatePickerValue:date];
[tester waitForViewWithAccessibilityLabel:@"Date Selection" value:@"Jun 17, 1965" traits:UIAccessibilityTraitNone];
}
选择器
- (void)testSelectingAPickerRow{
[tester selectPickerViewRowWithTitle:@"Charlie"];
NSOperatingSystemVersion iOS8 = {8, 0, 0};
[tester waitForViewWithAccessibilityLabel:@"Call Sign" value:@"Charlie" traits:UIAccessibilityTraitNone];
}
ModalView
点击alertView上的选项 取消
- (void)testInteractionWithAnAlertView{
[tester tapViewWithAccessibilityLabel:@"UIAlertView"];
[tester waitForViewWithAccessibilityLabel:@"Alert View"];
[tester waitForViewWithAccessibilityLabel:@"Message"];
[tester waitForTappableViewWithAccessibilityLabel:@"Cancel"];
[tester waitForTappableViewWithAccessibilityLabel:@"Continue"];
[tester tapViewWithAccessibilityLabel:@"Continue"];
[tester waitForAbsenceOfViewWithAccessibilityLabel:@"Message"];
}
点击底部弹窗
- (void)testInteractionWithAnActionSheet{
[tester tapViewWithAccessibilityLabel:@"UIActionSheet"];
[tester waitForViewWithAccessibilityLabel:@"Action Sheet"];
[tester waitForTappableViewWithAccessibilityLabel:@"Destroy"];
[tester waitForTappableViewWithAccessibilityLabel:@"A"];
[tester waitForTappableViewWithAccessibilityLabel:@"B"];
}
点击获取系统的activity
- (void)testInteractionWithAnActivityViewController{
if (!NSClassFromString(@"UIActivityViewController")) {
return;
}
[tester tapViewWithAccessibilityLabel:@"UIActivityViewController"];
[tester waitForTappableViewWithAccessibilityLabel:@"Copy"];
[tester waitForTappableViewWithAccessibilityLabel:@"Mail"];
}
CollectionView
点击Item
- (void)testTappingItems{
[tester tapItemAtIndexPath:[NSIndexPath indexPathForItem:199 inSection:0] inCollectionViewWithAccessibilityIdentifier:@"CollectionView Tests CollectionView"];
[tester waitForViewWithAccessibilityLabel:@"Last Cell" traits:UIAccessibilityTraitSelected];
[tester tapItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] inCollectionViewWithAccessibilityIdentifier:@"CollectionView Tests CollectionView"];
[tester waitForViewWithAccessibilityLabel:@"First Cell" traits:UIAccessibilityTraitSelected];
}
ScrollView
滑动
///down up right left 为ScrollView上的控件
- (void)testScrollingToTapOffscreenViews{
[tester tapViewWithAccessibilityLabel:@"Down"];
[tester tapViewWithAccessibilityLabel:@"Up"];
[tester tapViewWithAccessibilityLabel:@"Right"];
[tester tapViewWithAccessibilityLabel:@"Left"];
}
Landscape
切换横屏
- (void)beforeAll{
[system simulateDeviceRotationToOrientation:UIDeviceOrientationLandscapeLeft];
[tester scrollViewWithAccessibilityIdentifier:@"Test Suite TableView" byFractionOfSizeHorizontal:0 vertical:-0.2];
}
- (void)afterAll{
[system simulateDeviceRotationToOrientation:UIDeviceOrientationPortrait];
[tester waitForTimeInterval:0.5];
}
TextField
textField输入文字
登录注册时常常使用这种方式进行行为描述
- (void)testWaitingForSearchFieldToBecomeFirstResponder{
[tester tapViewWithAccessibilityLabel:nil traits:UIAccessibilityTraitSearchField];
[tester waitForFirstResponderWithAccessibilityLabel:nil traits:UIAccessibilityTraitSearchField];
[tester enterTextIntoCurrentFirstResponder:@"text"];
[tester waitForViewWithAccessibilityLabel:nil value:@"text" traits:UIAccessibilityTraitSearchField];
}
WebView
点击链接
- (void)testTappingLinks {
[tester tapViewWithAccessibilityLabel:@"A link"];
[tester waitForViewWithAccessibilityLabel:@"Page 2"];
}
输入文本
- (void)testEnteringText {
[tester tapViewWithAccessibilityLabel:@"Input Label"];
[tester enterTextIntoCurrentFirstResponder:@"Keyboard text"];
}
滚动Scroll
- (void)testScrolling {
// Off screen, the web view will need to be scrolled down
[tester waitForViewWithAccessibilityLabel:@"Footer"];
}
Background
进入后台操作
- (void)testBackgroundApp {
[tester waitForViewWithAccessibilityLabel:@"Start"];
[system deactivateAppForDuration:5];
[tester waitForViewWithAccessibilityLabel:@"Back"];
}
PullToRefesh
下拉刷新
-(void) testPullToRefreshByAccessibilityLabelWithDuration{
UITableView *tableView;
[tester waitForAccessibilityElement:NULL view:&tableView withIdentifier:@"Test Suite TableView" tappable:NO];
[tester pullToRefreshViewWithAccessibilityLabel:@"Table View" pullDownDuration:KIFPullToRefreshInAboutThreeSeconds];
[tester waitForViewWithAccessibilityLabel:@"Bingo!"];
[tester waitForAbsenceOfViewWithAccessibilityLabel:@"Bingo!"];
[tester waitForTimeInterval:5.0f]; //make sure the PTR is finished.
}
Typing
复制粘贴
- (void)testEnteringTextIntoViewWithAccessibilityLabel{
[tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:2];
[tester tapViewWithAccessibilityLabel:@"Select All"];
[tester tapViewWithAccessibilityLabel:@"Cut"];
[tester enterText:@"Yo" intoViewWithAccessibilityLabel:@"Greeting"];
[tester waitForViewWithAccessibilityLabel:@"Greeting" value:@"Yo" traits:UIAccessibilityTraitNone];
}
清除
- (void)testClearingAndEnteringTextIntoViewWithAccessibilityLabel
{
[tester clearTextFromAndThenEnterText:@"Yo" intoViewWithAccessibilityLabel:@"Greeting"];
}
两个textField联动
- (void)testEnteringReturnCharacterIntoViewWithAccessibilityLabel
{
[tester enterText:@"Hello\n" intoViewWithAccessibilityLabel:@"Other Text"];
[tester waitForFirstResponderWithAccessibilityLabel:@"Greeting"];
[tester enterText:@", world\n" intoViewWithAccessibilityLabel:@"Greeting" traits:UIAccessibilityTraitNone expectedResult:@"Hello, world"];
}
输入Emoj表情
- (void)testEnteringEmojiCharactersIntoViewWithAccessibilityLabel{
NSString *text = @" 😓He😤ll👿o";
[tester clearTextFromAndThenEnterText:text intoViewWithAccessibilityLabel:@"Greeting"];
UITextField * tf = (UITextField*)[tester waitForViewWithAccessibilityLabel:@"Greeting"];
XCTAssertTrue([tf.text isEqualToString:text]);
}
预期TextField的输入
- (void)testThatBackspaceDeletesOneCharacter{
[tester enterText:@"hi\bello" intoViewWithAccessibilityLabel:@"Other Text" traits:UIAccessibilityTraitNone expectedResult:@"hello"];
[tester waitForViewWithAccessibilityLabel:@"Greeting" value:@"Deleted something." traits:UIAccessibilityTraitNone];
UIView *textView = [tester waitForViewWithAccessibilityLabel:@"Other Text"];
XCTAssertEqualObjects([tester textFromView:textView], @"hello");
}
CascadingFailure
失败用例测试
- (void)testCascadingFailure{
KIFExpectFailure([system failA]);
KIFExpectFailureWithCount([system failA], 4);
}
补充Tips
按钮的title、类的title,可以直接做为访问标签
如果UI组件被键盘挡住了,需要先退掉键盘
如果UI组件不在屏幕范围内,不可以访问,但是滚动视图,可以访问,且会出现在可视范围。
无法访问系统自己的弹窗。例如app想定位用户,不能自动点击允许;但是app自己的弹窗,可以操作的
类内部的多个测试方法的测试顺序,是无序的
类与类的测试顺序,是无序的
可以将某个测试类或者测试方法给disable掉
最后
到目前为止,我们应该对KIF的使用性有很好的了解,脑子里也应该有不少主意,大概了解如何利用这个高效的功能测试工具来测试你自己的应用程序。由于KIF测试用例是OCUnit的子类,并在标准的Xcode5测试框架下运行,你可以使用持续集成来跑这些测试。当你干别的事情的时候,你拥有了一个能够像人的手指一样触控的机器人去测试你的应用程序。