iOS自動化測試之KIF使用分享
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測試框架下執行,你可以使用持續整合來跑這些測試。當你幹別的事情的時候,你擁有了一個能夠像人的手指一樣觸控的機器人去測試你的應用程式。
參考文獻:
推薦閱讀
相關文章
- iOS自動化測試驅動工具探索iOS
- 測試開發之自動化篇-自動化測試框架設計框架
- iOS自動化測試調研方案iOS
- 自動化測試系列 —— UI自動化測試UI
- UI自動化測試之AirtestUIAI
- 介面自動化測試工程實踐分享
- AutoRunner 功能自動化測試專案實訓之自動化測試原理(一)
- 【自動化測試入門】自動化測試思維
- InstrumentDriver,對iOS自動化測試說 Yes!iOS
- 使用PostMan進行自動化測試Postman
- 使用 PostMan 進行自動化測試Postman
- 使用 testng 做介面自動化測試
- 測試開發之介面篇-使用K6完成介面自動化測試
- Android 自動化測試之 MonkeyAndroid
- 前端自動化測試之葵花寶典前端
- [IOS]IOS如何模擬弱網進行自動化測試iOS
- 推薦淘測試的InstrumentDriver(iOS下的自動化測試)iOS
- 軟體測試:自動化測試
- 自動化裝置測試與自動化測試的區別
- iOS appium UI 自動化測試配置可控 xpathiOSAPPUI
- IOS自動化測試環境搭建(Python & Java)iOSPythonJava
- iOS平臺如何進行app自動化測試?iOSAPP
- 實用測試技能分享:jmeter+Jenkins效能測試自動化搭建JMeterJenkins
- 自動化測試理解
- 自動化測試思路
- airTest自動化測試AI
- 介面自動化測試
- API自動化測試API
- 自動化測試框架框架
- 自動化元件測試元件
- 試著使用 jmeter 實現介面自動化測試JMeter
- HamronyOS 自動化測試框架使用指南框架
- 使用 Postman 實現 API 自動化測試PostmanAPI
- [android]android自動化測試十三之monkeyRunner自動化框架Android框架
- 如何學習自動化測試?從手工測試到自動化測試的過程…
- 自動化測試之控制元件點選控制元件
- 手工測試和自動化測試 BattleBAT
- 自動化測試系列(三)|UI測試UI