iOS 单元测试和界面测试教程

发表于:2017-12-14 11:28

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:默默熊    来源:51Testing软件测试网采编

  虽然写测试程序不是一件容易令人着迷的事情,但它却是非常必要的。因为测试可以避免你炫酷的应用成为一个错误百出的垃圾。如果你正在阅读本教程,你应该已经意识到需要编写测试程序来测试你的代码和界面了,但你可能还不知道如何在Xcode中进行测试。</p>
  <p>也许你已经有了一个可以“工作”的应用程序,但还没有为它建立测试程序,而且你希望在扩展你的应用时测试可以覆盖所有的改动。也许你已经写了一些测试程序,但不确定它们是否正确。又或者你正在写一个应用,希望能能够同步进行测试。</p>
  <p>这个教程展示了如何使用Xcode中的测试导航来测试你的应用模型和异步方法;如何使用存根(stub)和模拟对象来和库或者系统对象进行互动;如何测试应用的用户界面和性能,以及如何使用代码覆盖工具。在这个过程中你会遇到一些测试高手的常用词汇。在本教程的结尾,你将使用aplomb工具来将依赖注入到你的被测试系统中去!</p>
  <p>测试,测试…</p>
  什么是测试?
  <p>在开始写任何测试程序之前,先考虑一个最基础的问题:你要测什么?如果你的目标是扩展现有的应用程序,你应该首先为你打算修改的组件编写测试。</p>
  <p>具体来说,测试应该覆盖以下内容:</p><p><li>核心功能:模型类和方法,以及它们与控制器的交互
  <li>最常用到的用户操作流程
  <li>边界条件
  <li>要解的bug</p>
  FIRST: 测试的最佳实践
  <p>缩写FIRST描述了一套简洁有效的单元测试标准。这些标准是:</p><li>快(F):测试应该执行的尽可能快,这样人们就不会介意运行它们。
  <li>独立/隔离(I):测试应该相互独立,有各自的建立(setup)和拆卸(teardown)过程。
  <li>重复性(R):每次运行测试都应获得相同的结果。外部数据和并发问题会可能导致间歇性故障。
  <li>自我验证(S):测试应该是全自动的;输出的结果要么是“通过”,要么是“失败”,而不能是输出一个只有程序员看得懂的日志文件。
  <li>及时(T):理想情况下,应该先写测试代码再写对应的生产代码。
  <p>遵循FIRST原则,将使你的测试清晰明了、并且真的可以对开发有帮助,而不是成为应用开发的阻碍。</p>
  让我们开始吧
  <p>下载,解压,打开并查看两个准备好了的起始项目 BullsEye 和 halftunes。</p>
  <p>BullsEye 是基于“iOS学徒”教学中的一个示例应用;我已经将其中的游戏逻辑提取到bullseyegame类中并添加了一种新的玩法。在右下角的分段控件可以让用户选择玩法:要么是移动滑块来尽可能地接近目标值;或者是通过猜测滑块的位置来得分。用户当前的玩法选择将被作为默认值存储起来。</p>
  <p>Halftuness是来自 NSURLSession教程中的示例应用程序,代码已经被更新到了Swift 3。用户可以通过iTunes API查询歌曲,然后下载和播放歌曲的试听片段。(译注:这个项目对新手可能会复杂些,所以我写了个分析。)</p>
  <p>让我们开始测试吧!</p>
  Xcode中的单元测试
  创建一个单元测试的目标
  Xcode中的测试导航栏提供了非常简便的进行测试的方法;你将使用它来创建测试目标和为你的应用执行测试程序。
  打开BullsEye工程,按?+5切换到测试导航栏。
  单击左下角的“+”按钮,然后从菜单选择 ”新的单元测试目标…:
  接受默认名称BullsEyeTests。当测试目标在测试导航栏中出现后,单击它在编辑器中打开。如果BullsEyeTests没有自动出现,试试切换到另一个导航栏,再切回。
  <p>自动生成的测试类模板会导入<code>XCTest</code>并定义一个XCTestCase的子类<code>BullsEyeTests</code>,以及setup(),teardown()和示例测试方法。有三种方法可以运行测试类:
  </p><ol>
  <li>从菜单上选择Product->Test 或者按 ?+U。 这会执行所有的测试类。</li>
  <li>点击测试导航栏中的箭头按钮。</li>
  <li>点击分隔栏上的菱形标志。</li>
  </ol>
  <p>您也可以点击每个测试方法所对应的菱形标志来执行该方法,无论是在测试导航栏还是在分隔栏上点都可以。</p>
  <p>尝试不同的方法来执行测试,感受一下所需的时间,还有它们看起来像什么。因为现在测试程序还没有做任何事,所以执行会很快!</p>
  <p>当所有的测试都通过后,菱形标志将变绿,并显示一个对号。点击在testPerformanceExample()方法结尾处的灰色菱形按钮打开性能结果展示:</p>
  <code>testPerformanceExample()</code>这个方法用不到,可以删除。</p>
  使用<code>XCTAssert</code>测试模型
  <p>首先,你将使用XCTAssert测试BullEye的一个核心功能:BullsEyeGame对象在每一轮中计算出的分数是否都正确?打开BullsEyeTests.swift,在import语句的下方加入这一行:</p>
  <p><pre><code>@testable import BullsEye</p></code></pre><p>这使得测试类可以访问BullsEye中的类和方法。在BullsEyeTests类的顶部,添加属性:</p>
  <p><pre><code>var gameUnderTest: BullsEyeGame!</p></code></pre><p>在setup()中创建一个新的BullsEyeGame对象,放在对super的调用的后面:</p>
  <p><pre><code>gameUnderTest = BullsEyeGame()</code>
  <code>gameUnderTest.startNewGame()</code></pre><p>这将在类中创建一个SUT(被测试系统)对象,所以在这个测试类中的所有测试方法都可以访问该SUT对象的属性和方法。</p>
  在这里,你还可以调用游戏的startNewGame方法,创建一个目标值。你的许多测试都将使用该目标值来检验游戏是否正确地计算了得分。
  <p>在你忘记之前,在<code>tearDown()</code>中释放该SUT对象,将下面的代码放在对super的调用之前:</p>
  <pre>gameUnderTest=nil</pre>
  <pre>注:在setup() 中建立SUT并在tearDown()中释放它是一个最佳实践。这可以确保每一个测试在一个干净的状态中开始。乔恩瑞德写的相关文章中有更多的讨论。</pre>
  现在你已经准备好写你的第一个测试方法了!使用下面的代码替换testExample()
  <pre>// XCTAssert to test model func testScoreIsComputed(){ // 1. given let guess = gameUnderTest.targetValue + 5 // 2. when _ = gameUnderTest.check(guess:guess) // 3. then XCTAssertEqual(gameUnderTest.scoreRound,95,"Score computed from guess is wrong") }</pre>
  测试方法的名字总是以test开始,紧跟着的是对该测试方法的描述。
  以假设(given), 当(when) 和然后(then)三段的方式来组织一个测试方法是一个很好的做法:
  在given部分,设置任何测试所需要的值:在这个例子中创建了一个猜测值,并指定它和目标值的差。
  在when部分,执行被测试代码:执行gameUndertest.check(_:)
  在then部分,使用断言来判断结果(在这个例子中,gameUnderTest.scoreRound 的值是 100-5=95)。如果测试失败则会打印出一个消息。
  通过在分隔栏或测试导航器中单击菱形标志来执行测试。应用程序将会被构建和执行,然后菱形标志会变成绿色的对号!
  注:要想查看完整的XCTestAssertions列表,按下 ? 并点击XCTAssertEqual来打开 XCTestAssertions.h,或去查看苹果的官方文档。
  注:测试的Given-When-Then结构起源于行为驱动开发(BDD)。这是一种对客户友好,术语简单的命名方式。其它的命名系统还有Arrange-Act-Assert (组织-行动-断言)和Assemble-Activate-Assert(组装-激活-断言)。</p>
  对测试进行调试
  我在BullsEyeGame中故意放置了一个bug,现在你去把它找出来。要发现这个bug,将testScoreIsComputed方法重命名为testScoreIsComputedWhenGuessGTTarget,然后复制、粘贴建立一个新方法testScoreIsComputedWhenGuessLTTarget。
  在这个测试方法中,在given段,将.targetValue+5 改为-5。其它部分不变:
  <pre>func testScoreIsComputedWhenGuessLTTarget() { // 1. given let guess = gameUnderTest.targetValue - 5 // 2. when _ = gameUnderTest.check(guess: guess) // 3. then XCTAssertEqual(gameUnderTest.scoreRound, 95, "Score computed from guess is wrong") }</pre>
  guess和targetValue之间的差仍然是5,所以得分应该还是95。
  在断点导航栏中,添加一个测试失败断点;当测试方法发出失败断言时,测试将停止执行。
  <p>执行测试:它应该停在XCTAssertEqual处,并伴随一个测试失败的提示。在调试控制台检查gameUnderTest和guess变量的值:</p>
  guess 等于 targetValue - 5 但得分(scoreRound)是105,不是95! (译注:targetValue是随机产生的,因而你看到的值很可能会和图中的不同)
  <p>为了进一步调查,我们采取通常的调试过程:在when陈述与BullsEyeGame.swift中导致了差异的check(_:)处分别设置一个断点。然后再次执行行测试,断点生效后单步跳过赋值行来检查应用中difference变量的值:</p>
  我们发现,问题出在difference是负值,所以得分是 100-(-5) ;修复方法是使用绝对值。在check(_:)中,打开正确行,删除错误行。
  <p>移除这两个断点并再次执行测试以确认它现在是成功的了。</p>

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号