iOS 自动化测试的那些干货

520lyh 7年前
   <h2>前言</h2>    <p>如果有测试大佬发现内容不对,欢迎指正,我会及时修改。</p>    <p>大多数的iOS App(没有持续集成)迭代流程是这样的</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f9100832fb8d27ad76855d5eb50029e4.png"></p>    <p>也就是说,测试是发布之前的最后一道关卡。如果bug不能在测试中发现,那么bug</p>    <p>就会抵达用户,所以测试的 <strong>完整性</strong> 和可靠性十分重要。</p>    <p>目前,大多数App还停留在人工测试阶段,人工测试投入的成本最低,能够保证核心功能的使用,而且测试人员不需要会写代码。</p>    <p>但是,在很多测试场景下,人工测试的效率太低,容易出错。举两个常见的例子:</p>    <ul>     <li>一个App的核心功能,在每一次发布版本前的测试必定会跑一遍所有的测试用例,不管对应的业务在当前版本有没有变化(天知道开发在做业务A的时候,对业务B有没有影响),如果这次测出新的bug,测试人员在下一次发版测试中,又不得不做这些重复的工作。</li>     <li>开发在写API请求相关代码的时候没有做数据容错,测试在人工测试的时候都是正常的数据,所以测试通过。上线了之后,后台配置数据的时候出了点小问题,导致大面积崩溃,boom~。</li>    </ul>    <p>然后,老板就要过来找你了</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/598ea5b174d4329d7585b95f34e4817d.jpg"></p>    <p>本文所讲解的均是基于XCode 8.2.1,有些概念可能不适用于低版本的XCode</p>    <h2>自动化测试</h2>    <p>自动化测试就是写一些测试代码,用代码代替人工去完成模块和业务的测试。</p>    <p>其实不管是开发还是测试,如果你在不断的做重复性工作的时候,就应该问自己一个问题:是不是有更高效的办法?</p>    <p>自动化测试有很多优点:</p>    <ul>     <li>测试速度快,避免重复性的工作</li>     <li>避免regression,让开发更有信心去修改和重构代码(个人认为最大的优点)</li>     <li>具有一致性。</li>     <li>有了自动化测试,持续集成(CI)会变得更可靠。</li>     <li>迫使开发人员写出更高质量的代码。(自动化测试不通过,代码不允许合并)</li>    </ul>    <p>当然,自动化测试也有一些缺点。</p>    <ul>     <li>开发和维护成本高。</li>     <li>不能完全替代人工测试。</li>     <li>无法完全保证测试的准确性 – 让代码去判断一段逻辑是否正确很容易,但是,让代码判断一个控件显示是否正确却没那么容易。</li>    </ul>    <p>所以,在做自动化测试之前,首先要问自己几个问题?</p>    <ul>     <li>这个测试业务的变动是否频繁?</li>     <li>这个测试业务是否属于核心功能?</li>     <li>编写测试代码的成本有多少?</li>     <li>自动化测试能保证测试结果的准确么?</li>    </ul>    <p>通常,我们会选择那些 <strong>业务稳定,需要频繁测试的部分来编写自动化测试脚本</strong> ,其余的采用人工测试,人工测试仍然是iOS App开发中不可缺少的一部分。</p>    <h2>测试种类</h2>    <p>从是否接触源代码的角度来分类:测试分为黑盒和白盒(灰盒就是黑盒白盒结合,这里不做讨论)。</p>    <p>白盒测试的时候,测试人员是可以直接接触待测试App的源代码的。白盒测试更多的是单元测试,测试人员针对各个单元进行各种可能的输入分析,然后测试其输出。白盒测试的测试代码通常由iOS开发编写。</p>    <p>黑盒测试。黑盒测试的时候,测试人员不需要接触源代码。是从App层面对其行为以及UI的正确性进行验证,黑盒测试由iOS测试完成。</p>    <p>从业务的层次上来说,测试金字塔如图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/584a8b3d052b81f4f2d64236fd8847ea.png"></p>    <p>而iOS测试通常只有以下两个层次:</p>    <ul>     <li>Unit,单元测试,保证每一个类能够正常工作</li>     <li>UI,UI测试,也叫做集成测试,从业务层的角度保证各个业务可以正常工作。</li>    </ul>    <h2>框架选择</h2>    <p>啰里八嗦讲的这么多,自动化测试的效率怎么样,关键还是在测试框架上。那么,如何选择测试框架呢?框架可以分为两大类: <strong>XCode内置的</strong> 和 <strong>三方库</strong> 。</p>    <p>选择框架的时候有几个方面要考虑</p>    <ul>     <li>测试代码编写的成本</li>     <li>是否可调式</li>     <li>框架的稳定性</li>     <li>测试报告(截图,代码覆盖率,…)</li>     <li>WebView的支持(很多App都用到了H5)</li>     <li>自定义控件的测试</li>     <li>是否需要源代码</li>     <li>能否需要连着电脑</li>     <li>是否支持CI(持续集成)</li>     <li>….</li>    </ul>    <p>我们首先来看看XCode内置的框架: <strong>XCTest</strong> 。 <strong>XCTest</strong> 又可以分为两部分: <strong>Unit Test</strong> 和 <strong>UI Test</strong> ,分别对应 <strong>单元测试</strong> 和 <strong>UI测试</strong> 。有一些三方的测试库也是基于XCTest框架的,这个在后文会讲到。由于是Apple官方提供的,所以这个框架会不断完善。</p>    <p>成熟的三方框架通常提供了很多封装好的有好的接口,笔者综合对比了一些,推荐以下框架:</p>    <p>单元测试:</p>    <p>以下三个框架都是BDD( Behavior-driven development ) – 行为驱动开发。行为驱动开发简单来说就是先定义行为,然后定义测试用例,接着再编写代码。 实践中发现,通常没有那么多时间来先定义行为,不过BDD中的domain-specific language (DSL)能够很好的描述用例的行为 。</p>    <ul>     <li><a href="/misc/goto?guid=4958870679218712387" rel="nofollow,noindex">Kiwi</a> 老牌测试框架</li>     <li><a href="/misc/goto?guid=4958539577374748108" rel="nofollow,noindex">specta</a> 另一个BDD优秀框架</li>     <li><a href="/misc/goto?guid=4958851214628394802" rel="nofollow,noindex">Quick</a> 三个项目中Star最多,支持OC和Swift,优先推荐。</li>    </ul>    <p>UI测试</p>    <ul>     <li><a href="/misc/goto?guid=4958543037995489543" rel="nofollow,noindex">KIF</a> 基于XCTest的测试框架,调用私有API来控制UI,测试用例用Objective C或Swift编写。</li>     <li><a href="/misc/goto?guid=4959620647481196538" rel="nofollow,noindex">appium</a> 基于Client – Server的测试框架。App相当于一个Server,测试代码相当于Client,通过发送JSON来操作APP,测试语言可以是任意的,支持android和iOS。</li>    </ul>    <p>篇幅有限,本文会先介绍XCtest,接着三方的Unit框架会以Quick为例,UI Test框架侧重分析KIF,appium仅仅做原理讲解。</p>    <h2>XCTest</h2>    <p>对于XCTest来说,最后生成的是一个bundle。bundle是不能直接执行的,必须依赖于一个宿主进程。关于XCTest进行单元测试的基础(XCode的使用,异步测试,性能测试,代码覆盖率等),我在这篇文章里讲解过,这里不再详细讲解。</p>    <ul>     <li><a href="/misc/goto?guid=4959746793716735489" rel="nofollow,noindex">iOS 单元测试之XCTest详解</a></li>    </ul>    <h3>单元测试用例</h3>    <p>比如,我有以下一个函数:</p>    <pre>  <code class="language-objectivec">//验证一段Text是否有效。(不能以空字符开头,不能为空)  - (BOOL)validText:(NSString *)texterror:(NSError *__autoreleasing *)error{  }  </code></pre>    <p>那么,我该如何为这个函数编写单元测试的代码?通常,需要考虑以下用例:</p>    <ol>     <li>输入以空白字符或者换行符开头的,error不为空,返回 NO</li>     <li>输入正确的内容,error为空,返回YES</li>     <li>输入为nil,error不为空,返回 NO (边界条件)</li>     <li>输入为非NSString类型,验证不通过,返回NO (错误输入)</li>     <li>特殊输入字符(标点符号,非英文等等)</li>    </ol>    <h3>UI测试</h3>    <p>UI测试是模拟用户操作,进而从业务处层面测试。关于XCTest的UI测试,建议看看WWDC 2015的这个视频:</p>    <ul>     <li><a href="/misc/goto?guid=4959713919092567881" rel="nofollow,noindex">UI Testing in Xcode</a></li>    </ul>    <p>关于UI测试,有几个核心类需要掌握</p>    <ul>     <li><a href="/misc/goto?guid=4959746793832458376" rel="nofollow,noindex">XCUIApplication</a> 测试应用的代理</li>     <li><a href="/misc/goto?guid=4959746793915537490" rel="nofollow,noindex">XCUIElement</a> 一个UI上可见的视图对象</li>     <li><a href="/misc/goto?guid=4959746794002411653" rel="nofollow,noindex">XCUIElementQuery</a> 查找XCUIElement</li>    </ul>    <p>UI测试还有一个核心功能是UI Recording。选中一个UI测试用例,然后点击图中的小红点既可以开始UI Recoding。你会发现:</p>    <p>随着点击模拟器,自动合成了测试代码。(通常自动合成代码后,还需要手动的去调整)</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a050848685a312b98286ecd8f4e377e3.png"></p>    <p>在写UI测试用例的时候要注意:测试行为而不是测试代码。比如,我们测试这样一个case</p>    <p>进入Todo首页,点击add,进入添加页面,输入文字,点击save。</p>    <p>测试效果如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/cac1d2718d39ce69b80ca128267f4570.gif"></p>    <p>对应测试代码:</p>    <pre>  <code class="language-objectivec">- (void)testAddNewItems{      //获取app代理      XCUIApplication *app = [[XCUIApplicationalloc] init];      //找到第一个tabeview,就是我们想要的tableview      XCUIElement * table = [app.tableselementBoundByIndex:0];      //记录下来添加之前的数量      NSIntegeroldCount = table.cells.count;      //点击Add      [app.navigationBars[@"ToDo"].buttons[@"Add"] tap];      //找到Textfield      XCUIElement *inputWhatYouWantTodoTextField = app.textFields[@"Input what you want todo"];      //点击Textfield      [inputWhatYouWantTodoTextFieldtap];      //输入字符      [inputWhatYouWantTodoTextFieldtypeText:@"somethingtodo"];      //点击保存      [app.navigationBars[@"Add"].buttons[@"Save"] tap];      //获取当前的数量      NSIntegernewCount = table.cells.count;      //如果cells的数量加一,则认为测试成功      XCTAssert(newCount == oldCount + 1);  }  </code></pre>    <p>这里是通过前后tableview的row数量来断言成功或者失败。</p>    <h3>等待</h3>    <p>通常,在视图切换的时候有转场动画,我们需要等待动画结束,然后才能继续,否则query的时候很可能找不到我们想要的控件。</p>    <p>比如,如下代码等待VC转场结束,当query只有一个table的时候,才继续执行后续的代码。</p>    <pre>  <code class="language-objectivec">[self expectationForPredicate:[NSPredicatepredicateWithFormat:@"self.count = 1"]            evaluatedWithObject:app.tables                        handler:nil];  [self waitForExpectationsWithTimeout:2.0 handler:nil];  //后续代码....  </code></pre>    <p>Tips: 当你的UI结构比较复杂的时候,比如各种嵌套childViewController,使用XCUIElementQuery的代码会很长,也不好维护。</p>    <p>另外,UI测试还会在每一步操作的时候截图,方便对测试报告进行验证。</p>    <h3>查看测试结果</h3>    <p>使用基于XCTest的框架,可以在XCode的report navigator中查看测试结果。</p>    <p><img src="https://simg.open-open.com/show/0dba6daadc05ee73d51e230e50bbd1f7.png"></p>    <p>其中:</p>    <ul>     <li>Tests 用来查看详细的测试过程</li>     <li>Coverage 用来查看代码覆盖率</li>     <li>Logs 用来查看测试的日志</li>     <li>点击图中的红色框指向的图标可以看到每一步UI操作的截图</li>    </ul>    <p>除了利用XCode的GUI,还可以通过后文提到的命令行工具来测试,查看结果。</p>    <h3>Stub/Mock</h3>    <p>首先解释两个术语:</p>    <ul>     <li>mock 表示一个模拟对象</li>     <li>stub 追踪方法的调用,在方法调用的时候返回指定的值。</li>    </ul>    <p>通常,如果你采用纯存的XCTest,推荐采用 OCMock 来实现mock和stub,单元测试的三方库通常已集成了stub和mock。</p>    <p>那么,如何使用mock呢?举个官方的例子:</p>    <pre>  <code class="language-objectivec">//mock一个NSUserDefaults对象  iduserDefaultsMock = OCMClassMock([NSUserDefaultsclass]);  //在调用stringForKey的时候,返回http://testurl  OCMStub([userDefaultsMock  stringForKey:@"MyAppURLKey"]).andReturn(@"http://testurl");  </code></pre>    <p>再比如,我们要测试打开其他App,那么如何判断确实打开了其他App呢?</p>    <pre>  <code class="language-objectivec">idapp = OCMClassMock([UIApplicationclass]);  OCMStub([appsharedInstance]).andReturn(app);  OCMVerify([appopenURL:url]   </code></pre>    <p>使用Stub可以让我们很方便的实现这个。</p>    <p>关于OCMock的使用,推荐看看objc.io的这篇文章</p>    <ul>     <li>置换测试: Mock, Stub 和其他</li>    </ul>    <h2>Quick</h2>    <p>Quick是建立在 XCTestSuite 上的框架,使用 XCTestSuite 允许你动态创建测试用例。所以,使用Quick,你仍让可以使用XCode的测试相关GUI和命令行工具。</p>    <p>使用Quick编写的测试用例看起来是这样子的:</p>    <pre>  <code class="language-objectivec">importQuick  importNimble     class TableOfContentsSpec: QuickSpec {    overridefuncspec() {      describe("the 'Documentation' directory") {        it("has everything you need to get started") {          letsections = 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") {            letyou = You(awesome: true)            expect{you.submittedAnIssue}.toEventually(beTruthy())          }        }      }    }  }  </code></pre>    <p>BDD的框架让测试用例的目的更加明确,测试是否通过更加清晰。使用Quick,测试用例分为两种:</p>    <h3>单独的用例 – 使用it来描述</h3>    <p>it有两个参数,</p>    <ul>     <li>行为描述</li>     <li>行为的测试代码</li>    </ul>    <p>比如,以下测试Dolphin行为,它具有行为 is friendly 和 is smart</p>    <pre>  <code class="language-objectivec">//Swift代码  class DolphinSpec: QuickSpec {    overridefuncspec() {      it("is friendly") {        expect(Dolphin().isFriendly).to(beTruthy())      }         it("is smart") {        expect(Dolphin().isSmart).to(beTruthy())      }    }  }  </code></pre>    <p>可以看到,BDD的核心是行为。也就是说,需要关注的是一个类提供哪些行为。</p>    <h3>用例集合,用describe和context描述</h3>    <p>比如,验证dolphin的click行为的时候,我们需要两个用例。一个是 is loud ,一个是 has a high frequency ,就可以用describe将用例组织起来。</p>    <pre>  <code class="language-objectivec">class DolphinSpec: QuickSpec {    overridefuncspec() {      describe("a dolphin") {        describe("its click") {          it("is loud") {            letclick = Dolphin().click()            expect(click.isLoud).to(beTruthy())          }             it("has a high frequency") {            letclick = Dolphin().click()            expect(click.hasHighFrequency).to(beTruthy())          }        }      }    }  }  </code></pre>    <p>context可以指定用例的条件:</p>    <p>比如</p>    <pre>  <code class="language-objectivec">describe("its click") {      context("when the dolphin is not near anything interesting") {        it("is only emitted once") {          expect(dolphin!.click().count).to(equal(1))        }      }  }  </code></pre>    <p>除了这些之外,Quick也支持一些切入点,进行测试前的配置:</p>    <ul>     <li>beforeEach</li>     <li>afterEach</li>     <li>beforeAll</li>     <li>afterAll</li>     <li>beforeSuite</li>     <li>afterSuite</li>    </ul>    <h3>Nimble</h3>    <p>由于Quick是基于XCTest,开发者当然可以收使用断言来定义测试用例成功或者失败。Quick提供了一个更有好的Framework来进行这种断言: <a href="/misc/goto?guid=4959629859297039907" rel="nofollow,noindex">Nimble</a></p>    <p>比如,一个常见的XCTest断言如下:</p>    <pre>  <code class="language-objectivec">XCTAssertTrue(ConditionCode, "FailReason")  </code></pre>    <p>在出错的时候,会提示</p>    <p>XCAssertTrue failed, balabala</p>    <p>这时候,开发者要打个断点,查看下上下文,看看具体失败的原因在哪。</p>    <p>使用Nimble后,断言变成类似</p>    <pre>  <code class="language-objectivec">expect(1 + 1).to(equal(2))  expect(3) > 2  expect("seahorse").to(contain("sea"))  expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))  </code></pre>    <p>并且,出错的时候,提示信息会带着上下文的值信息,让开发者更容易的找到错误。</p>    <h2>让你的代码更容易单元测试</h2>    <p>测试的准确性和工作量很大程度上依赖于开发人员的代码质量。</p>    <p>通常,为了单元测试的准确性,我们在写函数(方法)的时候会借鉴一些函数式编程的思想。其中最重要的一个思想就是</p>    <ul>     <li>pure function(纯函数)</li>    </ul>    <p>何为Pure function?就是如果一个函数的输入一样,那么输出一定一样。</p>    <p>比如,这样的一个函数就不是pure function。因为它依赖于外部变量value的值。</p>    <pre>  <code class="language-objectivec">static NSIntegervalue = 0;     - (NSInteger)function_1{      value = value + 1;      return value;  }  </code></pre>    <p>而这个函数就是pure function,因为给定输入,输出一定一致。</p>    <pre>  <code class="language-objectivec">- (NSInteger)function_2:(NSInteger)base{      NSIntegervalue = base + 1;      return value;  }  </code></pre>    <p>所以, <strong>如果你写了一个没有参数,或者没有返回值的方法</strong> ,那么你要小心了,很可能这个方法很难测试。</p>    <h3>关于MVC</h3>    <p>在良好的MVC架构的App中,</p>    <ul>     <li>View只做纯粹的展示型工作,把用户交互通过各种方式传递到外部</li>     <li>Model只做数据存储类工作</li>     <li>Controller作为View和Model的枢纽,往往要和很多View和Model进行交互,也是自动化包括代码维护的痛点。</li>    </ul>    <p>所以,对Controller瘦身是iOS架构中比较重要的一环,一些通用的技巧包括:</p>    <p>逻辑抽离:</p>    <ul>     <li>网络请求独立。可以每个网络请求以Command模式封装成一个对象,不要直接在Controller调用 AFNetworking 。</li>     <li>数据存储独立。建立独立的Store类,用来做数据持久化和缓存。</li>     <li>共有数据服务化(协议)。比如登录状态等等,通过服务去访问,这样服务提供者之需要处理服务的质量,服务使用者则信任服务提供者的结果。</li>    </ul>    <p>Controller与View解耦合</p>    <ul>     <li>建立ViewModel层,这样Controller只需要和ViewModel进行交互。</li>     <li>建立UIView子类作为容器,将一些View放到容器后再把容器作为SubView添加到Controller里</li>     <li>建立可复用的Layout层,不管是AutoLayout还是手动布局。</li>    </ul>    <p>Controller与Controller解耦合</p>    <ul>     <li>建立页面路由。每一个界面都抽象为一个URL,跳转仅仅通过Intent或者URL跳转,这样两个Controller完全独立。</li>    </ul>    <p>如果你的App用Swift开发,那么面向协议编程和不可变的值类型会让你的代码更容易测试。</p>    <p>当然,iOS组建化对自动化测试的帮助也很大,因为不管是基础组件还是业务组件,都可以独立测试。组建化又是一个很大的课题,这里不深入讲解了。</p>    <h2>KIF</h2>    <p><a href="/misc/goto?guid=4959746794109733018" rel="nofollow,noindex">KIF</a> 的全称是Keep it functional。它是一个建立在XCTest的UI测试框架, 通过accessibility来定位具体的控件,再利用私有的API来操作UI。 由于是建立在XCTest上的,所以你可以完美的借助XCode的测试相关工具(包括命令行脚本)。</p>    <p>> KIF是个人非常推荐的一个框架,简单易用。</p>    <p>使用KIF框架强制要求你的代码支持accessibility。如果你之前没接触过,可以看看Apple的文档</p>    <ul>     <li><a href="/misc/goto?guid=4959746794195422170" rel="nofollow,noindex">Accessibility Programming Guide for iOS</a></li>    </ul>    <p>简单来说,accessibility能够让视觉障碍人士使用你的App。每一个控件都有一个描述AccessibilityLabel。在开启VoiceOver的时候,点击控件就可以选中并且听到对应的描述。</p>    <p>通常UIKit的控件是支持accessibility的,自定定义控件可以通过代码或者Storyboard上设置。</p>    <p>在Storyboard上设置:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a0f3bc91810249043db66fc92aac1eef.png"></p>    <ul>     <li>上面的通过Runtime Attributes设置(KVC)</li>     <li>下面的通过GUI来设置</li>    </ul>    <p>通过代码设置:</p>    <pre>  <code class="language-objectivec">[alertsetAccessibilityLabel:@"Label"];  [alertsetAccessibilityValue:@"Value"];  [alertsetAccessibilityTraits:UIAccessibilityTraitButton];  </code></pre>    <p>如果你有些Accessibility的经验,那么你肯定知道,像TableView的这种不应该支持VoiceOver的。我们可以用条件编译来只对测试Target进行设置:</p>    <pre>  <code class="language-objectivec">#ifdef DEBUG  [tableViewsetAccessibilityValue:@"Main List Table"];  #endif     #ifdef KIF_TARGET (这个值需要在build settings里设置)  [tableViewsetAccessibilityValue:@"Main List Table"];  #endif  </code></pre>    <p>使用KIF主要有两个核心类:</p>    <ul>     <li>KIFTestCase XCTestCase的子类</li>     <li>KIFUITestActor 控制UI,常见的三种是:点击一个View,向一个View输入内容,等待一个View的出现</li>    </ul>    <p>我们用KIF来测试添加一个新的ToDo</p>    <pre>  <code class="language-objectivec">- (void)testAddANewItem{      [testertapViewWithAccessibilityLabel:@"Add"];      [testerenterText:@"Create a test to do item" intoViewWithAccessibilityLabel:@"Input what you want todo"];      [testertapViewWithAccessibilityLabel:@"Save"];      [testerwaitForTimeInterval:0.2];      [testerwaitForViewWithAccessibilityLabel:@"Create a test to do item"];  }  </code></pre>    <h2>命令行</h2>    <p>自动化测试中,命令行工具可以非死book的开源项目:</p>    <ul>     <li><a href="/misc/goto?guid=4958822653748995612" rel="nofollow,noindex">xctool</a></li>    </ul>    <p>这是一个基于 xcodebuild 命令的扩展,在iOS自动化测试和持续集成领域很有用,而且它支持 -parallelize 并行测试多个bundle,大大提高测试效率。</p>    <p>安装XCTool,</p>    <pre>  <code class="language-objectivec">brewinstallxctool  </code></pre>    <p>使用</p>    <pre>  <code class="language-objectivec">path/to/xctool.sh \    -workspaceYourWorkspace.xcworkspace \    -schemeYourScheme \    -reporterplain:/path/to/plain-output.txt \    run-test  </code></pre>    <p>并且,xctool对于持续集成很有用,iOS常用的持续集成的server有两个:</p>    <ul>     <li><a href="/misc/goto?guid=4958533335774215227" rel="nofollow,noindex">Travis CI</a> 对于公开仓库(比如github)免费,私有仓库收费</li>     <li><a href="/misc/goto?guid=4958189353958478943" rel="nofollow,noindex">Jenkins</a> 免费</li>    </ul>    <h2>优化你的测试代码</h2>    <h3>准确的测试用例</h3>    <p>通常,你的你的测试用例分为三部分:</p>    <ul>     <li>配置测试的初始状态</li>     <li>对要测试的目标执行代码</li>     <li>对测试结果进行断言(成功 or 失败)</li>    </ul>    <h3>测试代码结构</h3>    <p>当测试用例多了,你会发现测试代码编写和维护也是一个技术活。通常,我们会从几个角度考虑:</p>    <ul>     <li>不要测试私有方法(封装是OOP的核心思想之一,不要为了测试破坏封装)</li>     <li>对用例分组(功能,业务相似)</li>     <li>对单个用例保证测试独立(不受之前测试的影响,不影响之后的测试),这也是测试是否准确的核心。</li>     <li>提取公共的代码和操作,减少copy/paste这类工作,测试用例是上层调用,只关心业务逻辑,不关心内部代码实现。</li>    </ul>    <p>一个常见的测试代码组织如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/24d8c62b6b17b0c49e5d4ef03aa12ebd.png"></p>    <h2>appium</h2>    <p>appium采用了Client Server的模式。对于App来说就是一个Server,基于 WebDriver JSON wire protocol 对实际的UI操作库进行了封装,并且暴露出RESTFUL的接口。然后测试代码通过HTTP请求的方式,来进行实际的测试。其中,实际驱动UI的框架根据系统版本有所不同:</p>    <ul>     <li>< 9.3 采用UIAutomation</li>     <li>>= 9.3 XCUITest</li>    </ul>    <p>原因也比较简单:Apple在10.0之后,移除了UIAutomation的支持,只支持XCUITest。</p>    <p><img src="https://simg.open-open.com/show/56e43ce63fb9814adba3cae8324cfcfe.png"></p>    <p>对比KIF,appium有它的优点:</p>    <ul>     <li>跨平台,支持iOS,Android</li>     <li>测试代码可以由多种语言编写,这对测试来说门槛更低</li>     <li>测试脚本独立与源代码和测试框架</li>    </ul>    <p>当然,任何框架都有缺点:</p>    <ul>     <li>自定义控件支持不好</li>     <li>WebView的支持不好</li>    </ul>    <h2>总结</h2>    <p>由于我不是专业的iOS测试,关于测试的一点见解如下:</p>    <ul>     <li>单元测试还是选择BDD框架,毕竟可读性高一些,推荐 Quick (Swift), Kiwi (Objective C)</li>     <li>UI测试优先推荐 <a href="/misc/goto?guid=4958543037995489543" rel="nofollow,noindex">KIF</a> ,如果需要兼顾安卓测试,或者测试人员对OC/Swift很陌生,可以采用 appium</li>    </ul>    <h2>参考资料</h2>    <ul>     <li><a href="/misc/goto?guid=4959746794376138339" rel="nofollow,noindex">Testing with Xcode</a> 官方文档,关于XCTest以及XCode有详细的讲解</li>     <li><a href="/misc/goto?guid=4959746794461226796" rel="nofollow,noindex">objc.io关于测试的资料</a> 对于官方文档的补充</li>     <li><a href="/misc/goto?guid=4959746794542056737" rel="nofollow,noindex">腾讯移动品质中心</a> 鹅厂移动品质中心,有很多好文章,强力推荐。</li>     <li><a href="/misc/goto?guid=4959746794631551764" rel="nofollow,noindex">基于 KIF 的 iOS UI 自动化测试和持续集成</a> 美团点评技术团队写的一篇博客</li>     <li><a href="/misc/goto?guid=4959746794714872633" rel="nofollow,noindex">testing-in-swift</a></li>     <li><a href="/misc/goto?guid=4959746794798689317" rel="nofollow,noindex">微信读书排版引擎自动化测试方案</a></li>    </ul>    <p>声明:本博客所有文章均为个人观点,与雇主没有任何关系。</p>    <p> </p>    <p>来自:http://ios.jobbole.com/93107/</p>    <p> </p>