KIF的全稱(chēng)是Keep it functional状飞。它是一個(gè)建立在XCTest的UI測(cè)試框架毫胜,通過(guò)accessibility來(lái)定位具體的控件,再利用私有的API來(lái)操作UI诬辈。由于是建立在XCTest上的酵使,所以你可以完美的借助XCode的測(cè)試相關(guān)工具(包括命令行腳本)。
特點(diǎn):
- 最小化迂回時(shí)間
繼承KIFTestCase焙糟,測(cè)試代碼都是使用OC編寫(xiě)口渔,最大程度減少了中間層。 - 配置簡(jiǎn)單
直接集成到XCode上穿撮,不需要安裝多余的包缺脉。 - 像用戶(hù)一樣測(cè)試
測(cè)試代碼模仿用戶(hù)操作,代碼很簡(jiǎn)單 - 自動(dòng)集成XCode 5以上的測(cè)試工具
在XCode上使用就像使用蘋(píng)果原生的測(cè)試框架一樣悦穿,支持XCode的各種測(cè)試工具攻礼。
按照KIF的的官方文檔進(jìn)行導(dǎo)入KIF,接下來(lái)以官方的demo為例,進(jìn)行KIF的相關(guān)操作的講解咧党。
目錄
- 使用描述
- Tapping
- Show/Hide
- Gesture
- TableView
- Picker
- ModalView
- CollectionView
- ScrollView
- Landscape
- WebView
- Background
- PullToRefesh
- CascadingFailure
- 補(bǔ)充Tips
使用描述
使用KIF主要有兩個(gè)核心類(lèi):
- KIFTestCase XCTestCase的子類(lèi)
- KIFUITestActor 控制UI秘蛔,常見(jiàn)的三種是:點(diǎn)擊一個(gè)View,向一個(gè)View輸入內(nèi)容,等待一個(gè)View的出現(xiàn)
KIF利用Accessibility來(lái)找元素.tapViewWithAccessibilityLabel
這也許是最常被用到的測(cè)試動(dòng)作方法深员。正如其名稱(chēng)所顯示的负蠕,它可以在給定的輔助標(biāo)簽?zāi)M在視圖上的觸擊。在大多數(shù)情況下倦畅,輔助標(biāo)簽和可視的文本標(biāo)簽(例如按鈕組件)是配套的遮糖。否則你就需要手動(dòng)設(shè)置輔助標(biāo)簽.
一些控件,諸如 UISwitch叠赐,更加復(fù)雜欲账,需要比簡(jiǎn)單的觸擊更復(fù)雜的步驟來(lái)觸發(fā)。KIF 提供了一個(gè)特殊的 setOn:forSwitchWithAccessibilityLabel: 方法來(lái)改變一個(gè)切換的狀態(tài).
導(dǎo)入KIF框架后芭概,在項(xiàng)目的Tests(如果還沒(méi)有Tests文件夾,可以先創(chuàng)建Tests)聲明一個(gè)測(cè)試類(lèi)赛不,必須以Tests結(jié)尾(如LoginTests),繼承自KIFTestCase罢洲。為了讓程序在運(yùn)行每個(gè)測(cè)試用例時(shí)能保證連貫性踢故,在每個(gè)類(lèi)中都聲明兩個(gè)方法beforeEach
與afterEach
保證測(cè)試過(guò)程中可以進(jìn)入想要執(zhí)行的測(cè)試頁(yè)面與退出測(cè)試頁(yè)面。
///進(jìn)入指定頁(yè)面
- (void)beforeEach{
[tester tapViewWithAccessibilityLabel:@"Tapping"];
}
///退出指定頁(yè)面
- (void)afterEach{
[tester tapViewWithAccessibilityLabel:@"Test Suite" traits:UIAccessibilityTraitButton];
}
Tapping
點(diǎn)擊控制器上標(biāo)記為T(mén)apViewController 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];
}
}
長(zhǎng)按操作
- (void)testLongPressingViewViewWithTraits{
[tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:2];
[tester tapViewWithAccessibilityLabel:@"Select All"];
}
切換按鈕狀態(tài)
- (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];
}
滑動(dòng)狀態(tài)條
- (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];
}
選取系統(tǒng)的圖片
- (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
一直尋找區(qū)域內(nèi)標(biāo)記為B的值為BB的可點(diǎn)擊的按鈕
- (void)testWaitingForViewWithValue{
NSLog(@"testWaitingForViewWithValue");
[tester waitForTappableViewWithAccessibilityLabel:@"B" value:@"BB" traits:UIAccessibilityTraitButton];
}
選擇視圖內(nèi)可點(diǎn)擊切換狀態(tài)的按鈕
- (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
手勢(shì)操作
滑動(dòng)
四個(gè)方向都可操作
typedef NS_ENUM(NSUInteger, KIFSwipeDirection) {
KIFSwipeDirectionRight,
KIFSwipeDirectionLeft,
KIFSwipeDirectionUp,
KIFSwipeDirectionDown
};
示例
- (void)testSwipingRight{
[tester swipeViewWithAccessibilityLabel:@"Swipe Me" inDirection:KIFSwipeDirectionRight];
[tester waitForViewWithAccessibilityLabel:@"Right"];
}
點(diǎn)擊
- (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的手勢(shì)滑動(dòng)
[tester swipeRowAtIndexPath:firstCellPath inTableView:tableView inDirection:KIFSwipeDirectionLeft];
點(diǎn)擊tableview上指定的某個(gè)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];
}
移動(dòng)
- (void)testMoveRowUpUsingNegativeRowIndexes{
[tester moveRowAtIndexPath:[NSIndexPath indexPathForRow:-1 inSection:1]
toIndexPath:[NSIndexPath indexPathForRow:-3 inSection:1] inTableViewWithAccessibilityIdentifier:@"TableView Tests Table"];
}
刪除
///刪除第一個(gè)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", @"");
}
設(shè)置Button狀態(tài)
- (void)testTogglingSwitch{
[tester setOn:NO forSwitchWithAccessibilityLabel:@"Table View Switch"];
[tester setOn:YES forSwitchWithAccessibilityLabel:@"Table View Switch"];
}
檢查元素是否在視圖上 當(dāng)元素在視圖上消失時(shí) 執(zhí)行操作
- (void)testButtonAbsentAfterRemoveFromSuperview{
UIView *view = [tester waitForViewWithAccessibilityLabel:@"Button"];
[view removeFromSuperview];
[tester waitForAbsenceOfViewWithAccessibilityLabel:@"Button"];
}
Picker
時(shí)間選擇器
- (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
點(diǎn)擊alertView上的選項(xiàng) 取消
- (void)testInteractionWithAnAlertView{
[tester tapViewWithAccessibilityLabel:@"UIAlertView"];
[tester waitForViewWithAccessibilityLabel:@"Alert View"];
[tester waitForViewWithAccessibilityLabel:@"Message"];
[tester waitForTappableViewWithAccessibilityLabel:@"Cancel"];
[tester waitForTappableViewWithAccessibilityLabel:@"Continue"];
[tester tapViewWithAccessibilityLabel:@"Continue"];
[tester waitForAbsenceOfViewWithAccessibilityLabel:@"Message"];
}
點(diǎn)擊底部彈窗
- (void)testInteractionWithAnActionSheet{
[tester tapViewWithAccessibilityLabel:@"UIActionSheet"];
[tester waitForViewWithAccessibilityLabel:@"Action Sheet"];
[tester waitForTappableViewWithAccessibilityLabel:@"Destroy"];
[tester waitForTappableViewWithAccessibilityLabel:@"A"];
[tester waitForTappableViewWithAccessibilityLabel:@"B"];
}
點(diǎn)擊獲取系統(tǒng)的activity
- (void)testInteractionWithAnActivityViewController{
if (!NSClassFromString(@"UIActivityViewController")) {
return;
}
[tester tapViewWithAccessibilityLabel:@"UIActivityViewController"];
[tester waitForTappableViewWithAccessibilityLabel:@"Copy"];
[tester waitForTappableViewWithAccessibilityLabel:@"Mail"];
}
CollectionView
點(diǎn)擊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
滑動(dòng)
///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輸入文字
登錄注冊(cè)時(shí)常常使用這種方式進(jìn)行行為描述
- (void)testWaitingForSearchFieldToBecomeFirstResponder{
[tester tapViewWithAccessibilityLabel:nil traits:UIAccessibilityTraitSearchField];
[tester waitForFirstResponderWithAccessibilityLabel:nil traits:UIAccessibilityTraitSearchField];
[tester enterTextIntoCurrentFirstResponder:@"text"];
[tester waitForViewWithAccessibilityLabel:nil value:@"text" traits:UIAccessibilityTraitSearchField];
}
WebView
點(diǎn)擊鏈接
- (void)testTappingLinks {
[tester tapViewWithAccessibilityLabel:@"A link"];
[tester waitForViewWithAccessibilityLabel:@"Page 2"];
}
輸入文本
- (void)testEnteringText {
[tester tapViewWithAccessibilityLabel:@"Input Label"];
[tester enterTextIntoCurrentFirstResponder:@"Keyboard text"];
}
滾動(dòng)Scroll
- (void)testScrolling {
// Off screen, the web view will need to be scrolled down
[tester waitForViewWithAccessibilityLabel:@"Footer"];
}
Background
進(jìn)入后臺(tái)操作
- (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
復(fù)制粘貼
- (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"];
}
兩個(gè)textField聯(lián)動(dòng)
- (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]);
}
預(yù)期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
失敗用例測(cè)試
- (void)testCascadingFailure{
KIFExpectFailure([system failA]);
KIFExpectFailureWithCount([system failA], 4);
}
補(bǔ)充Tips
按鈕的title惹苗、類(lèi)的title殿较,可以直接做為訪(fǎng)問(wèn)標(biāo)簽
如果UI組件被鍵盤(pán)擋住了,需要先退掉鍵盤(pán)
如果UI組件不在屏幕范圍內(nèi)桩蓉,不可以訪(fǎng)問(wèn)淋纲,但是滾動(dòng)視圖,可以訪(fǎng)問(wèn)院究,且會(huì)出現(xiàn)在可視范圍洽瞬。
無(wú)法訪(fǎng)問(wèn)系統(tǒng)自己的彈窗。例如app想定位用戶(hù)业汰,不能自動(dòng)點(diǎn)擊允許片任;但是app自己的彈窗,可以操作的
類(lèi)內(nèi)部的多個(gè)測(cè)試方法的測(cè)試順序蔬胯,是無(wú)序的
類(lèi)與類(lèi)的測(cè)試順序对供,是無(wú)序的
可以將某個(gè)測(cè)試類(lèi)或者測(cè)試方法給disable掉
最后
到目前為止,我們應(yīng)該對(duì)KIF的使用性有很好的了解氛濒,腦子里也應(yīng)該有不少主意产场,大概了解如何利用這個(gè)高效的功能測(cè)試工具來(lái)測(cè)試你自己的應(yīng)用程序。由于KIF測(cè)試用例是OCUnit的子類(lèi)舞竿,并在標(biāo)準(zhǔn)的Xcode5測(cè)試框架下運(yùn)行京景,你可以使用持續(xù)集成來(lái)跑這些測(cè)試。當(dāng)你干別的事情的時(shí)候骗奖,你擁有了一個(gè)能夠像人的手指一樣觸控的機(jī)器人去測(cè)試你的應(yīng)用程序确徙。
參考文獻(xiàn):
- 使用KIF 進(jìn)行 iOS UI 測(cè)試
- 自動(dòng)化測(cè)試框架: KIF和EarlGrey
- KIF 源碼庫(kù)
- 基于 KIF 的 iOS UI 自動(dòng)化測(cè)試和持續(xù)集成