zoukankan      html  css  js  c++  java
  • iOS自己主动化測试的那些干货

    前言

    假设有測试大佬发现内容不正确。欢迎指正,我会及时改动。

    大多数的iOS App(没有持续集成)迭代流程是这种

    也就是说。測试是公布之前的最后一道关卡。假设bug不能在測试中发现,那么bug
    就会抵达用户,所以測试的完整性可靠性十分重要。

    眼下,大多数App还停留在人工測试阶段,人工測试投入的成本最低,能够保证核心功能的使用,而且測试人员不须要会写代码。

    可是,在非常多測试场景下。人工測试的效率太低,easy出错。

    举两个常见的样例:

    • 一个App的核心功能,在每一次公布版本号前的測试必然会跑一遍全部的測试用例,无论相应的业务在当前版本号有没有变化(天知道开发在做业务A的时候,对业务B有没有影响),假设这次測出新的bug,測试人员在下一次发版測试中,又不得不做这些反复的工作。
    • 开发在写API请求相关代码的时候没有做数据容错,測试在人工測试的时候都是正常的数据,所以測试通过。

      上线了之后,后台配置数据的时候出了点小问题,导致大面积崩溃,boom~。

    然后,老板就要过来找你了

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


    自己主动化測试

    自己主动化測试就是写一些測试代码。用代码取代人工去完毕模块和业务的測试。

    事实上无论是开发还是測试,假设你在不断的做反复性工作的时候。就应该问自己一个问题:是不是有更高效的办法?

    自己主动化測试有非常多长处:

    • 測试速度快。避免反复性的工作
    • 避免regression。让开发更有信心去改动和重构代码(个人觉得最大的长处)
    • 具有一致性。
    • 有了自己主动化測试,持续集成(CI)会变得更可靠。
    • 迫使开发人员写出更高质量的代码。(自己主动化測试不通过,代码不同意合并)

    当然,自己主动化測试也有一些缺点。

    • 开发和维护成本高。

    • 不能全然替代人工測试。
    • 无法全然保证測试的准确性 - 让代码去推断一段逻辑是否正确非常easy,可是,让代码推断一个控件显示是否正确却没那么easy

    所以。在做自己主动化測试之前,首先要问自己几个问题?

    • 这个測试业务的变动是否频繁?
    • 这个測试业务是否属于核心功能?
    • 编写測试代码的成本有多少?
    • 自己主动化測试能保证測试结果的准确么?

    通常,我们会选择那些业务稳定。须要频繁測试的部分来编写自己主动化測试脚本,其余的採用人工測试。人工測试仍然是iOS App开发中必不可少的一部分。


    測试种类

    从是否接触源码的角度来分类:測试分为黑盒和白盒(灰盒就是黑盒白盒结合,这里不做讨论)。

    白盒測试的时候,測试人员是能够直接接触待測试App的源码的。

    白盒測试很多其它的是单元測试,測试人员针对各个单元进行各种可能的输入分析,然后測试其输出。白盒測试的測试代码通常由iOS开发编写。

    黑盒測试。

    黑盒測试的时候,測试人员不须要接触源码。

    是从App层面对其行为以及UI的正确性进行验证。黑盒測试由iOS測试完毕。

    从业务的层次上来说,測试金字塔如图:

    而iOS測试通常仅仅有下面两个层次:

    • Unit。单元測试,保证每个类能够正常工作
    • UI,UI測试,也叫做集成測试,从业务层的角度保证各个业务能够正常工作。

    框架选择

    啰里八嗦讲的这么多,自己主动化測试的效率怎么样。关键还是在測试框架上。

    那么,怎样选择測试框架呢?框架能够分为两大类:XCode内置的三方库

    选择框架的时候有几个方面要考虑

    • 測试代码编写的成本
    • 是否可调式
    • 框架的稳定性
    • 測试报告(截图,代码覆盖率,…)
    • WebView的支持(非常多App都用到了H5)
    • 自己定义控件的測试
    • 是否须要源码
    • 是否能须要连着电脑
    • 是否支持CI(持续集成)
    • ….

    我们首先来看看XCode内置的框架:XCTestXCTest又能够分为两部分:Unit TestUI 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. 输入以空白字符或者换行符开头的,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);
    }

    这里是通过前后tableview的row数量来断言成功或者失败。

    等待

    通常,在视图切换的时候有转场动画,我们须要等待动画结束,然后才干继续。否则query的时候非常可能找不到我们想要的控件。

    比方。例如以下代码等待VC转场结束,当query仅仅有一个table的时候,才继续运行兴许的代码。

    [self expectationForPredicate:[NSPredicate predicateWithFormat:@"self.count = 1"]
              evaluatedWithObject:app.tables
                          handler:nil];
    [self waitForExpectationsWithTimeout:2.0 handler:nil];
    //兴许代码....

    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");

    再比方,我们要測试打开其它App,那么怎样推断确实打开了其它App呢?

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

    使用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())
            }
          }
        }
      }
    }

    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())
        }
      }
    }

    能够看到。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())
            }
          }
        }
      }
    }

    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))
          }
        }
    }

    除了这些之外,Quick也支持一些切入点。进行測试前的配置:

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

    Nimble

    因为Quick是基于XCTest,开发人员当然能够收使用断言来定义測试用例成功或者失败。Quick提供了一个更有好的Framework来进行这种断言:Nimble

    比方,一个常见的XCTest断言例如以下:

    XCTAssertTrue(ConditionCode, "FailReason")

    在出错的时候,会提示

    XCAssertTrue failed, balabala

    这时候,开发人员要打个断点。查看下上下文。看看具体失败的原因在哪。

    使用Nimble后,断言变成类似

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

    而且。出错的时候,提示信息会带着上下文的值信息。让开发人员更easy的找到错误。


    让你的代码更easy单元測试

    測试的准确性和工作量非常大程度上依赖于开发人员的代码质量

    通常。为了单元測试的准确性,我们在写函数(方法)的时候会借鉴一些函数式编程的思想。

    当中最重要的一个思想就是

    • pure function(纯函数)

    何为Pure function?就是假设一个函数的输入一样,那么输出一定一样。

    比方。这种一个函数就不是pure function。因为它依赖于外部变量value的值。

    static NSInteger value = 0;
    
    - (NSInteger)function_1{
        value = value + 1;
        return value;
    }

    而这个函数就是pure function,因为给定输入,输出一定一致。

    - (NSInteger)function_2:(NSInteger)base{
        NSInteger value = base + 1;
        return value;
    }

    所以。假设你写了一个没有參数,或者没有返回值的方法,那么你要小心了。非常可能这种方法非常难測试。

    关于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开发。那么面向协议编程和不可变的值类型会让你的代码更easy測试。

    当然,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];

    假设你有些Accessibility的经验。那么你肯定知道。像TableView的这种不应该支持VoiceOver的。我们能够用条件编译来仅仅对測试Target进行设置:

    #ifdef DEBUG
    [tableView setAccessibilityValue:@"Main List Table"];
    #endif
    
    #ifdef KIF_TARGET (这个值须要在build settings里设置)
    [tableView setAccessibilityValue:@"Main List Table"];
    #endif

    使用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"];
    }

    命令行

    自己主动化測试中,命令行工具能够facebook的开源项目:

    这是一个基于xcodebuild命令的扩展,在iOS自己主动化測试和持续集成领域非常实用,而且它支持-parallelize并行測试多个bundle,大大提高測试效率。

    安装XCTool,

    brew install xctool

    使用

    path/to/xctool.sh 
      -workspace YourWorkspace.xcworkspace 
      -scheme YourScheme 
      -reporter plain:/path/to/plain-output.txt 
      run-test

    而且。xctool对于持续集成非常实用,iOS经常使用的持续集成的server有两个:

    • Travis CI 对于公开仓库(比方github)免费。私有仓库收费
    • Jenkins 免费

    优化你的測试代码

    准确的測试用例

    通常,你的你的測试用例分为三部分:

    • 配置測试的初始状态
    • 对要測试的目标运行代码
    • 对測试结果进行断言(成功 or 失败)

    測试代码结构

    当測试用例多了。你会发现測试代码编写和维护也是一个技术活。

    通常。我们会从几个角度考虑:

    • 不要測试私有方法(封装是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的支持不好

    总结

    因为我不是专业的iOS測试,关于測试的一点见解例如以下:

    • 单元測试还是选择BDD框架。毕竟可读性高一些,推荐Quick(Swift)。Kiwi(Objective C)
    • UI測试优先推荐KIF,假设须要兼顾安卓測试,或者測试人员对OC/Swift非常陌生,能够採用appium

    參考资料

    声明:本博客全部文章均为个人观点。与雇主没有不论什么关系。

  • 相关阅读:
    node.js 安装后怎么打开 node.js 命令框
    thinkPHP5 where多条件查询
    网站title中的图标
    第一次写博客
    Solution to copy paste not working in Remote Desktop
    The operation could not be completed. (Microsoft.Dynamics.BusinessConnectorNet)
    The package failed to load due to error 0xC0011008
    VS2013常用快捷键
    微软Dynamics AX的三层架构
    怎样在TFS(Team Foundation Server)中链接团队项目
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7356651.html
Copyright © 2011-2022 走看看