iOS 自動化測試框架 Google EarlGrey 嘗鮮

whoe84makeu 8年前發布 | 21K 次閱讀 iOS開發 移動開發

今天看到恒溫發的鏈接,加上最近項目在做 iOS 自動化框架的調研,趕緊嘗鮮了下。

框架主頁:https://github.com/google/EarlGrey

 <h2 id="框架簡介">框架簡介</h2>

 <p>EarlGrey is a native iOS UI automation test framework that enables you to write clear, concise tests.</p>

 <p>With the EarlGrey framework, you have access to enhanced synchronization features. EarlGrey automatically synchronizes with the UI, network requests, and various queues, but still allows you to manually implement customized timings, if needed.</p>

 <p>EarlGrey’s synchronization features help ensure that the UI is in a steady state before actions are performed. This greatly increases test stability and makes tests highly repeatable.</p>

 <p>EarlGrey works in conjunction with the XCTest framework and integrates with Xcode’s Test Navigator so you can run tests directly from Xcode or the command line (using xcodebuild).</p>

 <p>特性:</p>

 <ul> 
  <li>Synchronization</li>

 </ul>

 <p>Typically, you shouldn’t be concerned about synchronization as EarlGrey automatically synchronizes with the UI, network requests, main Dispatch Queue, and the main NSOperationQueue. To support cases where you want to wait for some event to occur before the next UI interaction happens, EarlGrey provides Synchronization APIs that allow you to control EarlGrey's synchronization behavior. You can use these APIs to increase the stability of your tests.</p>

 <ul> 
  <li>Visibility Checks</li>

 </ul>

 <p>EarlGrey uses screenshot differential comparison (also known as 'screenshot diffs') to determine the visibility of UI elements before interacting with them. As a result, you can be certain that a user can see and interact with the UI that EarlGrey interacts with.</p>

 <p>Note: Out-of-process (i.e. system generated) alert views and other modal dialogs that obscure the UI can interfere with this process.</p>

 <ul> 
  <li>User-Like Interaction</li>

 </ul>

 <p>Taps and swipes are performed using app-level touch events, instead of using element-level event handlers. Before every UI interaction, EarlGrey asserts that the elements being interacted with are actually visible (see Visibility Checks) and not just present in the view hierarchy. EarlGrey's UI interactions simulate how a real user would interact with your app's UI, and help you to find and fix the same bugs that users would encounter in your app.</p>

 <p>簡而言之,是一個內嵌式框架(以 framework 形式內嵌至應用中),用例繼承 XCTestCase ,本質上是 iOS 的 Unit test 。比較類似的框架是 KIF 。</p>

 <p>主要特性是:</p>

 <ul> 
  <li>同步性:需要等待的操作自動等待,媽媽不用擔心我的 wait 和 sleep 寫錯了</li>

  <li>可見性檢測:因為是直接對應用內對象操作,所以有可能給一個用戶看不到的元素發送觸控事件了。這個可以防止出現這種情況,瀏覽器使用的 Webdriver 里面也有類似特性</li>

  <li>模擬用戶操作:使用 app 級別的觸控對象,而非元素級別的事件觸發。簡而言之,屏幕上不可見的元素都操作不了了。</li>

 </ul>

 <h2 id="嘗鮮準備">嘗鮮準備</h2>

 <p>因為它的 Prerequisites 略復雜,所以直接用了官方 Example 。</p>

 <p>環境需求:<br />
  1. Xcode
  2. CocoaPod (1.0.0 beta 或者 0.39 stable 均可)</p>

    1、 git clone https://github.com/google/EarlGrey.git
    2、 在EarlGrey/Demo/EarlGreyExample執行 pod install 安裝依賴庫。如果你的 Pod 是 1.0.0 beta ,恭喜你,直接運行即可。如果是 0.39 stable ,請改成下面的內容:

    #
    # Copyright 2016 Google Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    # http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

EarlGreyExample

platform :ios, '8.0'

source '

PROJECT_NAME = 'EarlGreyExample' TEST_TARGET = 'EarlGreyExampleTests' SCHEME_FILE = 'EarlGreyExampleTests.xcscheme' TEST_TARGET_SWIFT = 'EarlGreyExampleSwiftTests' SCHEME_FILE_SWIFT = 'EarlGreyExampleSwiftTests.xcscheme'

xcodeproj PROJECT_NAME target TEST_TARGET, :exclusive => true do pod 'EarlGrey' end

target TEST_TARGET_SWIFT, :exclusive => true do pod 'EarlGrey' end

target TEST_TARGET do

project PROJECT_NAME

#

inherit! :search_paths

pod 'EarlGrey'

end

#

target TEST_TARGET_SWIFT do

project PROJECT_NAME

#

inherit! :search_paths

pod 'EarlGrey'

end

post_install do |installer| load('configure_earlgrey_pods.rb')

For each test target, you need to call the EarlGrey script's edit method once.

For the 'EarlGreyExampleTests' target.

configure_for_earlgrey(installer, PROJECT_NAME, TEST_TARGET, SCHEME_FILE)

For the 'EarlGreyExampleSwiftTests' target.

configure_for_earlgrey(installer, PROJECT_NAME, TEST_TARGET_SWIFT, SCHEME_FILE_SWIFT) end</pre>

3、 打開 EarlGreyExample.xcworkspace 就可以 run 了。

 <h2 id="示例用例執行及解析">示例用例執行及解析</h2>

 <p>應用支持模擬器,直接在模擬器上 Test 了一遍,全部通過。</p>

 <p>官方給了兩套用例,一套是 OC 寫的,另一套是 swift 寫的。內容一樣。里面的寫法很有學習價值。這里以 OC 為例,簡單記錄一下。</p>

 <pre class="brush:cpp; toolbar: true; auto-links: false;">- (void)testBasicSelection {

// Select the button with Accessibility ID "clickMe". [EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]; }

  • (void)testBasicSelectionAndAction { // Select and tap the button with Accessibility ID "clickMe". [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]

    performAction:grey_tap()];
    

    }

  • (void)testBasicSelectionAndAssert { // Select the button with Accessibility ID "clickMe" and assert it's visible. [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]

    assertWithMatcher:grey_sufficientlyVisible()];
    

    }

  • (void)testBasicSelectionActionAssert { // Select and tap the button with Accessibility ID "clickMe", then assert it's visible. [[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]

    performAction:grey_tap()]
    assertWithMatcher:grey_sufficientlyVisible()];
    

    }</pre>

    循序漸進,從找元素、對元素操作、找元素+斷言、對元素操作+斷言四個階段編寫。從這些用例看出,EarlGrey 的 API 中找元素與元素操作是分離的,而非像 KIF 那樣合并在一起。

    - (void)testSelectionOnMultipleElements {
    // This test will fail because both buttons are visible and match the selection.   // We add a custom error here to prevent the Test Suite failing.   NSError *error;
    [[EarlGrey selectElementWithMatcher:grey_sufficientlyVisible()]

    performAction:grey_tap() error:&error];
    

    if (error) { NSLog(@"Test Failed with Error : %@",[error description]); } }</pre>

    展示了如何捕獲 ERROR (寫法和一些老的 UIKit 函數類似,返回的是 error 的地址而非內容)。這里的 error 原因是有不止一個匹配的元素。

    - (void)testCollectionMatchers {
    id<GREYMatcher> visibleSendButtonMatcher =

    grey_allOf(grey_accessibilityID(@"ClickMe"), grey_sufficientlyVisible(), nil);
    

    [[EarlGrey selectElementWithMatcher:visibleSendButtonMatcher]

    performAction:grey_doubleTap()];
    

    }</pre>

    展示了如何使用多條件獲取元素。例子中的 grey_allOf(grey_accessibilityID(@"ClickMe"), grey_sufficientlyVisible(), nil) 是指這個 matcher 的獲取條件為:AccessibilityID = "ClickMe" AND visible 。最后的 nil 應該只是展示支持 nil 。

    - (void)testWithInRoot {
    // Second way to disambiguate: use inRoot to focus on a specific window or container.   // There are two buttons with accessibility id "Send", but only one is inside SendMessageView.   [[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Send")]

    inRoot:grey_kindOfClass([SendMessageView class])] performAction:grey_doubleTap()];
    

    }</pre>

    展示 inRoot 父 view 篩選器。視圖中有兩個元素有相同的 AccessibilityId,但其中一個父 view 是 SendMessageView 類型的。

    // Define a custom matcher for table cells that contains a date for a Thursday. - (id<GREYMatcher>)matcherForThursdays {
    MatchesBlock matches = ^BOOL(UIView *cell) {
      if ([cell isKindOfClass:[UITableViewCell class]]) {

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterLongStyle;
    NSDate *date = [formatter dateFromString:[[(UITableViewCell *)cell textLabel] text]];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSInteger weekday = [calendar component:NSCalendarUnitWeekday fromDate:date];
    return weekday == 5;
    

    } else {

    return false;
    

    } }; DescribeToBlock describe = ^void(id<GREYDescription> description) { [description appendText:@"Date for a Thursday"]; };

    return [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches

                                            descriptionBlock:describe];
    

    }

  • (void)testWithCustomMatcher { // Use the custom matcher. [[EarlGrey selectElementWithMatcher:[self matcherForThursdays]]

    performAction:grey_doubleTap()];
    

    }</pre>

    自定義 matcher 。有兩個部分。matcherBlock部分如果 block return true 就匹配,false 就不匹配。descriptionBlock 則是這個 matcher 的描述。用于 GREYBaseMatcher::describeTo:

    - (void)testTableCellOutOfScreen {
    // Go find one cell out of the screen.   [[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Cell30")]

    usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 50)
    

    onElementWithMatcher:grey_accessibilityID(@"table")]

    performAction:grey_doubleTap()];
    
    

    // Move back to top of the table. [[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Cell1")]

    usingSearchAction:grey_scrollInDirection(kGREYDirectionUp, 500)
    

    onElementWithMatcher:grey_accessibilityID(@"table")]

    performAction:grey_doubleTap()];
    

    }</pre>

    獲取屏幕外部元素。usingSearchAction:onElementWithMatcher 可以在父元素內通過指定 action (例子中用的是滑動)遍歷元素來查找指定元素。

    - (void)testCatchErrorOnFailure {
    // TapMe doesn't exist, but the test doesn't fail because we are getting a pointer to the error.   NSError *error;
    [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"TapMe")]

    performAction:grey_tap() error:&error];
    

    if (error) { NSLog(@"Error: %@", [error localizedDescription]); } }</pre>

    同樣是捕獲 Error ,只是為了說明當元素找不到時也會產生 error 。

    // Fade in and out an element. - (void)fadeInAndOut:(UIView *)element {
    [UIView animateWithDuration:1.0

                      delay:0.0
                    options:UIViewAnimationOptionCurveEaseOut
                 animations: ^{
                     element.alpha = 0.0;}
                 completion: ^(BOOL finished) {
                     [UIView animateWithDuration:1.0
                                           delay:0.0
                                         options:UIViewAnimationOptionCurveEaseIn
                                      animations: ^{
                                          element.alpha = 1.0;}
                                      completion: nil];
                 }];

}

// Define a custom action that applies fadeInAndOut to the selected element. - (id<GREYAction>)tapClickMe { return [GREYActionBlock actionWithName:@"Fade In And Out" constraints:nil performBlock: ^(id element, NSError __strong errorOrNil) { // First make sure element is attached to a window. if ([element window] == nil) { NSDictionary errorInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Element is not attached to a window", @"")}; errorOrNil = [NSError errorWithDomain:kGREYInteractionErrorDomain code:1 userInfo:errorInfo]; return NO; } else { [self fadeInAndOut:[element window]]; return YES; } }]; }

  • (void)testCustomAction { // Test using the custom action tapClickMe. [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"ClickMe")]

    performAction:[self tapClickMe]];
    

    }</pre>

    代碼略多,主要是為了展示封裝能力。GREYActionBlock 能把元素傳到 performBlock 的 element 參數,用于對元素執行指定操作。

    // Write a custom assertion that checks if the alpha of an element is equal to the expected value. - (id<GREYAssertion>)alphaEqual:(CGFloat)expectedAlpha {
    return [GREYAssertionBlock assertionWithName:@"Assert Alpha Equal"

                     assertionBlockWithError:^BOOL(UIView *element,
                                                   NSError *__strong *errorOrNil) {
                       // Assertions can be performed on nil elements. Make sure view isn’t nil.                          if (element == nil) {
                         *errorOrNil =
                             [NSError errorWithDomain:kGREYInteractionErrorDomain
                                                 code:kGREYInteractionElementNotFoundErrorCode
                                             userInfo:nil];
                         return NO;
                       }
                       return element.alpha == expectedAlpha;
                      }];
    

    }

 本文由用戶 whoe84makeu 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!