iOS自动化测试之KIF使用分享

作者: 五分钟学算法 | 来源:发表于2017-07-31 17:03 被阅读434次

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。为了让程序在运行每个测试用例时能保证连贯性,在每个类中都声明两个方法beforeEachafterEach 保证测试过程中可以进入想要执行的测试页面与退出测试页面。

///进入指定页面
- (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测试框架下运行,你可以使用持续集成来跑这些测试。当你干别的事情的时候,你拥有了一个能够像人的手指一样触控的机器人去测试你的应用程序。

参考文献:

推荐阅读

相关文章

  • iOS自动化测试之KIF使用分享

    KIF的全称是Keep it functional。它是一个建立在XCTest的UI测试框架,通过accessib...

  • KIF-- iOS UI 自动化测试探索

    KIF-- iOS UI 自动化测试探索 在我们探索自动化测试之前,我们先了解一下自动化测试的优缺点和还有,什么样...

  • iOS KIF自动化测试

    一. iOS单元测试,UI测试基本介绍 二. KIF简介 三. KIF集成 四. 测试用例编写 五. jenki...

  • 美团iOS技术文章

    1.深入理解Objective-C:Category 2.基于 KIF 的 iOS UI 自动化测试和持续集成 3...

  • iOS自动化测试KIF

    介绍KIF KIF的全称是Keep it functional。它是一个建立在XCTest的UI测试框架,通过ac...

  • iOS UI自动化测试框架KIF简单使用

    前言 iOS自动化测试一直不如Android的方便,最近没事看简单看了下,分别基本使用了一下自动化框架Quick和...

  • iOS自动化测试系统搭建--appuim

    我今天对于整体的自动化测试还是个小白,以前使用的都是iOS平台的自动化测试工具,现在为了测试小白可以使用,所以使用...

  • KIF自动化测试case

    最近公司需要,研究会自动化测试case 测试运行器会在运行时寻找所有以”test“开头的方法, 然后按字母顺序运行...

  • iOS UI自动化测试与代码覆盖率

    iOS UI自动化测试与代码覆盖率 iOS UI自动化测试与代码覆盖率

  • iOS基于KIF测试UI

    简介 常见的基于UI的测试框架有KIF, EarlGrey, WebDriverAgent, Frank, Cal...

网友评论

  • GeekFounder:终于找到一篇说自动化测试的例子,如果能附上Demo一份就更好了~~
    五分钟学算法:哈哈哈,可以参照官方的demo进行参考编写:smile:

本文标题: iOS自动化测试之KIF使用分享

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