• 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

    參考资料

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

  • 相关阅读:
    @ModelAttribute注解详细使用
    Spring MVC使用注解实现账户注册与登陆
    @PathVariable、@RequestHeader与@CookieValue注解的使用案例
    IDEA搭建我的第一个Spring MVC应用(附使用注解实现)
    JDBC自定义工具类
    JDBC与MySQL实现分页查询技术
    JavaWeb实战——记录网站上一次访问时间
    微信小程序使用setData修改数组中的指定下标的属性值
    Centos6.x 无法 ping 外网 --- 已解决
    Ckeditor失去焦点前保留光标位置
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7356651.html
Copyright © 2020-2023  润新知