养一只帮你测试的”无限猴子”

stone121 8年前
   <p>在上线之后发生了几次崩溃闪退, 需要紧急修复的情况之后, 我决定我要动手了…</p>    <p>分析了这几次情况之后, 发现其实大的逻辑都没有错, 但是一些小的东西特别容易出篓子, 例如说布尔条件写反了, 某个 @IBOutlet 的控件改名了, 删掉了, 忘了去 storyboard 里处理掉它, 就会发生 setValue: forUndefinedKey: 的错误, 本来我是想直接 swizzle 掉这个方法, 不让它抛出错误, 但是想想又觉得不值得. 难道终于要开始学一下怎么写测试了吗?</p>    <p>然后突然想起了之前好像看到过一个 UI 测试的框架, 可以自动帮忙测试 UI, 找到之后就开始用, 然后一发不可收拾.</p>    <h2>简介</h2>    <p>这个库让我想起了无限猴子理论, 其实也类似, 就是产生间隔一段事件就产生一个随机操作事件, 例如点击拖拽, 闪退的话是最容易发现的, 或者是你看到一些错误的数据和 UI 呈现.</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/74662f09d7542a1129347cad7056b60f.gif"></p>    <p>这个库分成两部分:</p>    <ol>     <li>主体是 SwiftMonkey, 依赖于 XCUITest, 调用了一些私有方法去发起操作事件</li>     <li>SwiftMonkeyPaws, 负责呈现操作事件的视觉效果, 上面的动图里, 那些小手掌就是 SwiftMonkeyPaws 制造出来的, 需要直接接入到 app 里面</li>    </ol>    <h2>接入流程</h2>    <p>官方文档目前还不是很详细, 我花了一点时间才把这个库给搞明白, 所以大概介绍一下接入流程.</p>    <p>包管理, 很简单嘛, 支持 Carthage 和 Cocoapods 两种方式, 想用哪个用哪个.</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f05d887c405e9971f2725f63ab6bb68e.png"></p>    <p>但使用 Cocoapods 的同学有一点事情要注意, 作者忘了 push podspec 到主仓库了, 所以我们 pod 里搜索和安装的都是 1.0.0 版本, 最低支持 iOS 9.0, 而最新的 1.0.1 版本最低支持 8.0.</p>    <p>解决方法也很简单, pod 的时候指定仓库就行了, 就像这样:</p>    <pre>  <code class="language-objectivec">pod 'SwiftMonkey', :git => 'https://github.com/zalando/SwiftMonkey.git'  </code></pre>    <p>安装完之后, 在 AppDelegate 里面我们需要初始化一下 SwiftMonkeyPaws, 有视觉效果毕竟会更好一点</p>    <pre>  <code class="language-objectivec">import SwiftMonkeyPaws    @UIApplicationMain  class AppDelegate: UIResponder, UIApplicationDelegate{     var paws: MonkeyPaws?     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {   #if DEBUG   if CommandLine.arguments.contains("--MonkeyPaws") {   paws = MonkeyPaws(view: window!)   }   #endif      return true   }  }  </code></pre>    <p>记得要在 AppDelegate 里声明一个 paws 去维持引用计数, 然后 MonkeyPaws 就会 swizzle 掉 UITouch 的方法, 让每次点击, 拖拽都会有相应的视觉效果.</p>    <p>这里我们看到一个 CommandLine.argments.contains(“—MonkeyPaws”) 可能会比较奇怪, 这段代码是为了区分开 app 是否跑在测试模式下的, 然后为了不在正式版里加入这段代码, 我们还加上了 compile flag 去判断是否编译这段代码. 直接加一个 ConfiguationSet 也行, 但不优雅, 也没必要…</p>    <p>接下里我们就去处理 UI 测试的代码:</p>    <pre>  <code class="language-objectivec">import SwiftMonkey    class UITest: XCTestCase{      override func setUp() {   super.setUp()      continueAfterFailure = false     let app = XCUIApplication()   app.launchArguments.append("--MonkeyPaws")   app.launch()   }  </code></pre>    <p>在 setup 方法里, 需要注意的就是最好把 continueAfterFailure 设为 false, 让代码出错时能够停留在出错的位置那里, 方便我们 DEBUG, 毕竟我们使用的不是常规的测试方法, 测试用例跟代码之间没有一一对应的关系.</p>    <p>还有一个就是加上参数 —MonkeyPaws 去区分运行和测试状态, 不加的话 paws 就不会运行了.</p>    <p>那么久该开始写用例了, 我用的方式比较粗暴</p>    <pre>  <code class="language-objectivec">func testMonkey() {   let application = XCUIApplication()     // Workaround for bug in Xcode 7.3. Snapshots are not properly updated   // when you initially call app.frame, resulting in a zero-sized rect.   // Doing a random query seems to update everything properly.   // TODO: Remove this when the Xcode bug is fixed!   _ = application.descendants(matching: .any).element(boundBy: 0).frame     let monkey = Monkey(frame: application.frame)      monkey.addXCTestTapAction(weight: 25)   monkey.addXCTestDragAction(weight: 200)   monkey.addXCTestTapAction(weight: 100)   monkey.addXCTestDragAction(weight: 30)      monkey.monkeyAround(iterations: 360000)  }  </code></pre>    <p>前面的代码是我照抄官方给的例子的, 不加的话会有 bug.</p>    <p>然后我们生成一只 Monkey, 然后给它添加一些动作, 其实还有什么各种 pinch, peek, pop 之类的, 但我的项目比较简单, 所以我就只加了点击和拖拽动作, weight 是间隔.</p>    <p>然后 monkeyAround 就是开始随机操作, iteration 是操作的次数, 操作满 360000 次就会停止.</p>    <p>这基本上就是我的用法, 里面还有一些挺有趣的东西, 之后有空的话我还会再探索一下, 例如添加多几个用例, 然后先跳转到新写的 ViewController 那里, 让这只猴子把里面的东西全都搞乱, 看看有啥 bug.</p>    <h2>使用体验</h2>    <p>到目前位置我用了这个库两三天, 每天中午去吃饭都会跑一下, 发现了几个 bug, 三个是低级错误, 两个比较隐晦, 主要是关于多次点击重复触发关键事件, 例如说一秒内连续点了七八次提交订单, 导致发出去七八个请求, 实际在网络情况不好的时候, 用户也有可能心急多次点击, 所以挺好的, 帮我提前预防了一些问题.</p>    <p>其实觉得无论是哪种情况, 都挺适合用一下这个库去找到一些低级的明显的 bug, 强烈推荐大家用一下.</p>    <p> </p>    <p>来自:https://kemchenj.github.io/2017/03/16/2017-03-16/</p>    <p> </p>