• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • 給你講講iOS自動化測試的那些干貨

    發表于:2017-11-27來源:CSDN作者:黃文臣點擊數: 標簽:
    一個App的核心功能,在每一次發布版本前的測試必定會跑一遍所有的測試用例,不管對應的業務在當前版本有沒有變化(天知道開發在做業務A的時候,對業務B有沒有影響),如果這次測

    大多數的iOS App(沒有持續集成)迭代流程是這樣的

    也就是說,測試是發布之前的最后一道關卡。如果bug不能在測試中發現,那么bug 
    就會抵達用戶,所以測試的完整性可靠性十分重要。

    目前,大多數App還停留在人工測試階段,人工測試投入的成本最低,能夠保證核心功能的使用,而且測試人員不需要會寫代碼。

    但是,在很多測試場景下,人工測試的效率太低,容易出錯。舉兩個常見的例子:

    • 一個App的核心功能,在每一次發布版本前的測試必定會跑一遍所有的測試用例,不管對應的業務在當前版本有沒有變化(天知道開發在做業務A的時候,對業務B有沒有影響),如果這次測出新的bug,測試人員在下一次發版測試中,又不得不做這些重復的工作。
    • 開發在寫API請求相關代碼的時候沒有做數據容錯,測試在人工測試的時候都是正常的數據,所以測試通過。上線了之后,后臺配置數據的時候出了點小問題,導致大面積崩潰,boom~。

    然后,老板就要過來找你了

    本文所講解的均是基于XCode 8.2.1,有些概念可能不適用于低版本的XCode


    自動化測試

    自動化測試就是寫一些測試代碼,用代碼代替人工去完成模塊和業務的測試。

    其實不管是開發還是測試,如果你在不斷的做重復性工作的時候,就應該問自己一個問題:是不是有更高效的辦法?

    自動化測試有很多優點:

    • 測試速度快,避免重復性的工作
    • 避免regression,讓開發更有信心去修改和重構代碼(個人認為最大的優點)
    • 具有一致性。
    • 有了自動化測試,持續集成(CI)會變得更可靠。
    • 迫使開發人員寫出更高質量的代碼。(自動化測試不通過,代碼不允許合并)

    當然,自動化測試也有一些缺點。

    • 開發和維護成本高。
    • 不能完全替代人工測試。
    • 無法完全保證測試的準確性 - 讓代碼去判斷一段邏輯是否正確很容易,但是,讓代碼判斷一個控件顯示是否正確卻沒那么容易。

    所以,在做自動化測試之前,首先要問自己幾個問題?

    • 這個測試業務的變動是否頻繁?
    • 這個測試業務是否屬于核心功能?
    • 編寫測試代碼的成本有多少?
    • 自動化測試能保證測試結果的準確么?

    通常,我們會選擇那些業務穩定,需要頻繁測試的部分來編寫自動化測試腳本,其余的采用人工測試,人工測試仍然是iOS App開發中不可缺少的一部分。


    測試種類

    從是否接觸源代碼的角度來分類:測試分為黑盒和白盒(灰盒就是黑盒白盒結合,這里不做討論)。

    白盒測試的時候,測試人員是可以直接接觸待測試App的源代碼的。白盒測試更多的是單元測試,測試人員針對各個單元進行各種可能的輸入分析,然后測試其輸出。白盒測試的測試代碼通常由iOS開發編寫。

    黑盒測試。黑盒測試的時候,測試人員不需要接觸源代碼。是從App層面對其行為以及UI的正確性進行驗證,黑盒測試由iOS測試完成。

    從業務的層次上來說,測試金字塔如圖:

    而iOS測試通常只有以下兩個層次:

    • Unit,單元測試,保證每一個類能夠正常工作
    • UI,UI測試,也叫做集成測試,從業務層的角度保證各個業務可以正常工作。

    框架選擇

    啰里八嗦講的這么多,自動化測試的效率怎么樣,關鍵還是在測試框架上。那么,如何選擇測試框架呢?框架可以分為兩大類:XCode內置的三方庫。

    選擇框架的時候有幾個方面要考慮

    • 測試代碼編寫的成本
    • 是否可調式
    • 框架的穩定性
    • 測試報告(截圖,代碼覆蓋率,…)
    • WebView的支持(很多App都用到了H5)
    • 自定義控件的測試
    • 是否需要源代碼
    • 能否需要連著電腦
    • 是否支持CI(持續集成)
    • ….

    我們首先來看看XCode內置的框架:XCTest。XCTest又可以分為兩部分:Unit Test 和 UI Test,分別對應單元測試UI測試。有一些三方的測試庫也是基于XCTest框架的,這個在后文會講到。由于是Apple官方提供的,所以這個框架會不斷完善。

    成熟的三方框架通常提供了很多封裝好的有好的接口,筆者綜合對比了一些,推薦以下框架:

    單元測試:

    以下三個框架都是BDD(Behavior-driven development) - 行為驅動開發。行為驅動開發簡單來說就是先定義行為,然后定義測試用例,接著再編寫代碼。 實踐中發現,通常沒有那么多時間來先定義行為,不過BDD中的domain-specific language (DSL)能夠很好的描述用例的行為。

    • Kiwi 老牌測試框架
    • specta 另一個BDD優秀框架
    • Quick 三個項目中Star最多,支持OC和Swift,優先推薦。

    UI測試

    • KIF 基于XCTest的測試框架,調用私有API來控制UI,測試用例用Objective C或Swift編寫。
    • appium 基于Client - Server的測試框架。App相當于一個Server,測試代碼相當于Client,通過發送JSON來操作APP,測試語言可以是任意的,支持android和iOS。

    篇幅有限,本文會先介紹XCtest,接著三方的Unit框架會以Quick為例,UI Test框架側重分析KIF,appium僅僅做原理講解。


    XCTest

    對于XCTest來說,最后生成的是一個bundle。bundle是不能直接執行的,必須依賴于一個宿主進程。關于XCTest進行單元測試的基礎(XCode的使用,異步測試,性能測試,代碼覆蓋率等),我在這篇文章里講解過,這里不再詳細講解。

    單元測試用例

    比如,我有以下一個函數:

    //驗證一段Text是否有效。(不能以空字符開頭,不能為空)
    - (BOOL)validText:(NSString *)text error:(NSError *__autoreleasing *)error{
    }
    • 1
    • 2
    • 3

    那么,我該如何為這個函數編寫單元測試的代碼?通常,需要考慮以下用例:

    1. 輸入以空白字符或者換行符開頭的,error不為空,返回 NO
    2. 輸入正確的內容,error為空,返回YES
    3. 輸入為nil,error不為空,返回 NO (邊界條件)
    4. 輸入為非NSString類型,驗證不通過,返回NO (錯誤輸入)
    5. 特殊輸入字符(標點符號,非英文等等)

    UI測試

    UI測試是模擬用戶操作,進而從業務處層面測試。關于XCTest的UI測試,建議看看WWDC 2015的這個視頻

    關于UI測試,有幾個核心類需要掌握

    UI測試還有一個核心功能是UI Recording。選中一個UI測試用例,然后點擊圖中的小紅點既可以開始UI Recoding。你會發現:

    隨著點擊模擬器,自動合成了測試代碼。(通常自動合成代碼后,還需要手動的去調整)

    在寫UI測試用例的時候要注意:測試行為而不是測試代碼。比如,我們測試這樣一個case

    進入Todo首頁,點擊add,進入添加頁面,輸入文字,點擊save。

    測試效果如下:

    對應測試代碼:

    - (void)testAddNewItems{
        //獲取app代理
        XCUIApplication *app = [[XCUIApplication alloc] init];
        //找到第一個tabeview,就是我們想要的tableview
        XCUIElement * table = [app.tables elementBoundByIndex:0];
        //記錄下來添加之前的數量
        NSInteger oldCount = table.cells.count;
        //點擊Add
        [app.navigationBars[@"ToDo"].buttons[@"Add"] tap];
        //找到Textfield
        XCUIElement *inputWhatYouWantTodoTextField = app.textFields[@"Input what you want todo"];
        //點擊Textfield
        [inputWhatYouWantTodoTextField tap];
        //輸入字符
        [inputWhatYouWantTodoTextField typeText:@"somethingtodo"];
        //點擊保存
        [app.navigationBars[@"Add"].buttons[@"Save"] tap];
        //獲取當前的數量
        NSInteger newCount = table.cells.count;
        //如果cells的數量加一,則認為測試成功
        XCTAssert(newCount == oldCount + 1);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    這里是通過前后tableview的row數量來斷言成功或者失敗。

    等待

    通常,在視圖切換的時候有轉場動畫,我們需要等待動畫結束,然后才能繼續,否則query的時候很可能找不到我們想要的控件。

    比如,如下代碼等待VC轉場結束,當query只有一個table的時候,才繼續執行后續的代碼。

    [self expectationForPredicate:[NSPredicate predicateWithFormat:@"self.count = 1"]
              evaluatedWithObject:app.tables
                          handler:nil];
    [self waitForExpectationsWithTimeout:2.0 handler:nil];
    //后續代碼....
    • 1
    • 2
    • 3
    • 4
    • 5

    Tips: 當你的UI結構比較復雜的時候,比如各種嵌套childViewController,使用XCUIElementQuery的代碼會很長,也不好維護。

    另外,UI測試還會在每一步操作的時候截圖,方便對測試報告進行驗證。

    查看測試結果

    使用基于XCTest的框架,可以在XCode的report navigator中查看測試結果。

    其中:

    • Tests 用來查看詳細的測試過程
    • Coverage 用來查看代碼覆蓋率
    • Logs 用來查看測試的日志
    • 點擊圖中的紅色框指向的圖標可以看到每一步UI操作的截圖

    除了利用XCode的GUI,還可以通過后文提到的命令行工具來測試,查看結果。

    Stub/Mock

    首先解釋兩個術語:

    • mock 表示一個模擬對象
    • stub 追蹤方法的調用,在方法調用的時候返回指定的值。

    通常,如果你采用純存的XCTest,推薦采用OCMock來實現mock和stub,單元測試的三方庫通常已集成了stub和mock。

    那么,如何使用mock呢?舉個官方的例子:

    //mock一個NSUserDefaults對象
    id userDefaultsMock = OCMClassMock([NSUserDefaults class]);
    //在調用stringForKey的時候,返回http://testurl
    OCMStub([userDefaultsMock 
    stringForKey:@"MyAppURLKey"]).andReturn(@"http://testurl");
    • 1
    • 2
    • 3
    • 4
    • 5

    再比如,我們要測試打開其他App,那么如何判斷確實打開了其他App呢?

    id app = OCMClassMock([UIApplication class]);
    OCMStub([app sharedInstance]).andReturn(app);
    OCMVerify([app openURL:url] 
    • 1
    • 2
    • 3

    使用Stub可以讓我們很方便的實現這個。

    關于OCMock的使用,推薦看看objc.io的這篇文章


    Quick

    Quick是建立在XCTestSuite上的框架,使用XCTestSuite允許你動態創建測試用例。所以,使用Quick,你仍讓可以使用XCode的測試相關GUI和命令行工具。

    使用Quick編寫的測試用例看起來是這樣子的:

    import Quick
    import Nimble
    
    class TableOfContentsSpec: QuickSpec {
      override func spec() {
        describe("the 'Documentation' directory") {
          it("has everything you need to get started") {
            let sections = Directory("Documentation").sections
            expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))
            expect(sections).to(contain("Installing Quick"))
          }
    
          context("if it doesn't have what you're looking for") {
            it("needs to be updated") {
              let you = You(awesome: true)
              expect{you.submittedAnIssue}.toEventually(beTruthy())
            }
          }
        }
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    BDD的框架讓測試用例的目的更加明確,測試是否通過更加清晰。使用Quick,測試用例分為兩種:

    單獨的用例 - 使用it來描述

    it有兩個參數,

    • 行為描述
    • 行為的測試代碼

    比如,以下測試Dolphin行為,它具有行為is friendlyis smart

    //Swift代碼
    class DolphinSpec: QuickSpec {
      override func spec() {
        it("is friendly") {
          expect(Dolphin().isFriendly).to(beTruthy())
        }
    
        it("is smart") {
          expect(Dolphin().isSmart).to(beTruthy())
        }
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,BDD的核心是行為。也就是說,需要關注的是一個類提供哪些行為。

    用例集合,用describe和context描述

    比如,驗證dolphin的click行為的時候,我們需要兩個用例。一個是is loud,一個是has a high frequency,就可以用describe將用例組織起來。

    class DolphinSpec: QuickSpec {
      override func spec() {
        describe("a dolphin") {
          describe("its click") {
            it("is loud") {
              let click = Dolphin().click()
              expect(click.isLoud).to(beTruthy())
            }
    
            it("has a high frequency") {
              let click = Dolphin().click()
              expect(click.hasHighFrequency).to(beTruthy())
            }
          }
        }
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    context可以指定用例的條件:

    比如

    describe("its click") {
        context("when the dolphin is not near anything interesting") {
          it("is only emitted once") {
            expect(dolphin!.click().count).to(equal(1))
          }
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    除了這些之外,Quick也支持一些切入點,進行測試前的配置:

    • beforeEach
    • afterEach
    • beforeAll
    • afterAll
    • beforeSuite
    • afterSuite

    Nimble

    由于Quick是基于XCTest,開發者當然可以收使用斷言來定義測試用例成功或者失敗。Quick提供了一個更有好的Framework來進行這種斷言:Nimble

    比如,一個常見的XCTest斷言如下:

    XCTAssertTrue(ConditionCode, "FailReason")
    • 1

    在出錯的時候,會提示

    XCAssertTrue failed, balabala

    這時候,開發者要打個斷點,查看下上下文,看看具體失敗的原因在哪。

    使用Nimble后,斷言變成類似

    expect(1 + 1).to(equal(2))
    expect(3) > 2
    expect("seahorse").to(contain("sea"))
    expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
    • 1
    • 2
    • 3
    • 4

    并且,出錯的時候,提示信息會帶著上下文的值信息,讓開發者更容易的找到錯誤。


    讓你的代碼更容易單元測試

    測試的準確性和工作量很大程度上依賴于開發人員的代碼質量。

    通常,為了單元測試的準確性,我們在寫函數(方法)的時候會借鑒一些函數式編程的思想。其中最重要的一個思想就是

    • pure function(純函數)

    何為Pure function?就是如果一個函數的輸入一樣,那么輸出一定一樣。

    比如,這樣的一個函數就不是pure function。因為它依賴于外部變量value的值。

    static NSInteger value = 0;
    
    - (NSInteger)function_1{
        value = value + 1;
        return value;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    而這個函數就是pure function,因為給定輸入,輸出一定一致。

    - (NSInteger)function_2:(NSInteger)base{
        NSInteger value = base + 1;
        return value;
    }
    • 1
    • 2
    • 3
    • 4

    所以,如果你寫了一個沒有參數,或者沒有返回值的方法,那么你要小心了,很可能這個方法很難測試。

    關于MVC

    在良好的MVC架構的App中,

    • View只做純粹的展示型工作,把用戶交互通過各種方式傳遞到外部
    • Model只做數據存儲類工作
    • Controller作為View和Model的樞紐,往往要和很多View和Model進行交互,也是自動化包括代碼維護的痛點。

    所以,對Controller瘦身是iOS架構中比較重要的一環,一些通用的技巧包括:

    邏輯抽離:

    • 網絡請求獨立??梢悦總€網絡請求以Command模式封裝成一個對象,不要直接在Controller調用AFNetworking。
    • 數據存儲獨立。建立獨立的Store類,用來做數據持久化和緩存。
    • 共有數據服務化(協議)。比如登錄狀態等等,通過服務去訪問,這樣服務提供者之需要處理服務的質量,服務使用者則信任服務提供者的結果。

    Controller與View解耦合

    • 建立ViewModel層,這樣Controller只需要和ViewModel進行交互。
    • 建立UIView子類作為容器,將一些View放到容器后再把容器作為SubView添加到Controller里
    • 建立可復用的Layout層,不管是AutoLayout還是手動布局。

    Controller與Controller解耦合

    • 建立頁面路由。每一個界面都抽象為一個URL,跳轉僅僅通過Intent或者URL跳轉,這樣兩個Controller完全獨立。

    如果你的App用Swift開發,那么面向協議編程和不可變的值類型會讓你的代碼更容易測試。

    當然,iOS組建化對自動化測試的幫助也很大,因為不管是基礎組件還是業務組件,都可以獨立測試。組建化又是一個很大的課題,這里不深入講解了。


    KIF

    KIF的全稱是Keep it functional。它是一個建立在XCTest的UI測試框架,通過accessibility來定位具體的控件,再利用私有的API來操作UI。由于是建立在XCTest上的,所以你可以完美的借助XCode的測試相關工具(包括命令行腳本)。

    > KIF是個人非常推薦的一個框架,簡單易用。

    使用KIF框架強制要求你的代碼支持accessibility。如果你之前沒接觸過,可以看看Apple的文檔

    簡單來說,accessibility能夠讓視覺障礙人士使用你的App。每一個控件都有一個描述AccessibilityLabel。在開啟VoiceOver的時候,點擊控件就可以選中并且聽到對應的描述。

    通常UIKit的控件是支持accessibility的,自定定義控件可以通過代碼或者Storyboard上設置。

    在Storyboard上設置:

    • 上面的通過Runtime Attributes設置(KVC)
    • 下面的通過GUI來設置

    通過代碼設置:

    [alert setAccessibilityLabel:@"Label"];
    [alert setAccessibilityValue:@"Value"];
    [alert setAccessibilityTraits:UIAccessibilityTraitButton];
    • 1
    • 2
    • 3

    如果你有些Accessibility的經驗,那么你肯定知道,像TableView的這種不應該支持VoiceOver的。我們可以用條件編譯來只對測試Target進行設置:

    #ifdef DEBUG
    [tableView setAccessibilityValue:@"Main List Table"];
    #endif
    
    #ifdef KIF_TARGET (這個值需要在build settings里設置)
    [tableView setAccessibilityValue:@"Main List Table"];
    #endif
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用KIF主要有兩個核心類:

    • KIFTestCase XCTestCase的子類
    • KIFUITestActor 控制UI,常見的三種是:點擊一個View,向一個View輸入內容,等待一個View的出現

    我們用KIF來測試添加一個新的ToDo

    - (void)testAddANewItem{
        [tester tapViewWithAccessibilityLabel:@"Add"];
        [tester enterText:@"Create a test to do item" intoViewWithAccessibilityLabel:@"Input what you want todo"];
        [tester tapViewWithAccessibilityLabel:@"Save"];
        [tester waitForTimeInterval:0.2];
        [tester waitForViewWithAccessibilityLabel:@"Create a test to do item"];
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    命令行

    自動化測試中,命令行工具可以facebook的開源項目:

    這是一個基于xcodebuild命令的擴展,在iOS自動化測試和持續集成領域很有用,而且它支持-parallelize并行測試多個bundle,大大提高測試效率。

    安裝XCTool,

    brew install xctool
    • 1

    使用

    path/to/xctool.sh \
      -workspace YourWorkspace.xcworkspace \
      -scheme YourScheme \
      -reporter plain:/path/to/plain-output.txt \
      run-test
    • 1
    • 2
    • 3
    • 4
    • 5

    并且,xctool對于持續集成很有用,iOS常用的持續集成的server有兩個:

    • Travis CI 對于公開倉庫(比如github)免費,私有倉庫收費
    • Jenkins 免費

    優化你的測試代碼

    準確的測試用例

    通常,你的你的測試用例分為三部分:

    • 配置測試的初始狀態
    • 對要測試的目標執行代碼
    • 對測試結果進行斷言(成功 or 失?。?/li>

    測試代碼結構

    當測試用例多了,你會發現測試代碼編寫和維護也是一個技術活。通常,我們會從幾個角度考慮:

    • 不要測試私有方法(封裝是OOP的核心思想之一,不要為了測試破壞封裝)
    • 對用例分組(功能,業務相似)
    • 對單個用例保證測試獨立(不受之前測試的影響,不影響之后的測試),這也是測試是否準確的核心。
    • 提取公共的代碼和操作,減少copy/paste這類工作,測試用例是上層調用,只關心業務邏輯,不關心內部代碼實現。

    一個常見的測試代碼組織如下:


    appium

    appium采用了Client Server的模式。對于App來說就是一個Server,基于WebDriver JSON wire protocol對實際的UI操作庫進行了封裝,并且暴露出RESTFUL的接口。然后測試代碼通過HTTP請求的方式,來進行實際的測試。其中,實際驅動UI的框架根據系統版本有所不同:

    • < 9.3 采用UIAutomation
    • >= 9.3 XCUITest

    原因也比較簡單:Apple在10.0之后,移除了UIAutomation的支持,只支持XCUITest。

    對比KIF,appium有它的優點:

    • 跨平臺,支持iOS,Android
    • 測試代碼可以由多種語言編寫,這對測試來說門檻更低
    • 測試腳本獨立與源代碼和測試框架

    當然,任何框架都有缺點:

    • 自定義控件支持不好
    • WebView的支持不好

    原文轉自:http://blog.csdn.net/hello_hwc/article/details/60957515

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>