初探 iOS 的单元测试(Unit Test)

发表于:2017-9-15 09:43

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

 作者:陳董Don    来源:51Testing软件测试网采编

  
  就像上面这张图一样,当我们的项目开始变得复杂以后,有时会在增加功能的同时伴随着 Bug 的产生。
  而对于已经上线的产品,我们又希望更新的版本不要将 Bug 带到使用者的面前,所以我们每次 release 前都会尽量地去做 test,
  在项目还小的时候,我们可能是通过人为的操作接口来看常用场景是否有问题,而这个测试过程也是重复的。
  所以还是想回到通过程式来进行测试,这次就先从 Unit Test开始吧。
  初步使用Unit Test
  引入测试框架
  在刚开始建立Xcode Project的时候,系统会问要不要加入Test相关的内容,如果创建的时候没有加入,可以在“File -> New -> Test -> UI Test/ Unit Test。
  建立后会得到一个继承 XCTestCase 的档案。
  需要注意的是,在这个类别中定义的测试方法,名称需要以“test开头”,并且“不返回内容”。
  被认可的测试方法,左侧会有个方块,用来显示测试是否成功。
  做一个简单的测试,可以看到左侧会有测试结果。
  Assert – 功能测试(1)
  我们这里做一个应用,在 HomeViewController 中,使用者可以通过 Slider 来改变 Key 1 以及 Key 2 的值( Slider min value 为 0 max value 为 10),
  当 Key1 为 3 并且 Key2 为 7 的时候,会自动解锁,并且push另外一个画面进来。
  我们这里写了一个方法“isUnlockSuccess()”用于判断是否成功解锁:
      func isUnlockSuccess(number1: Int, number2: Int) -> Bool {
          if number1 == 3 && number2 == 7 {
              return true
          } else {
              return false
          }
      }
  那么我们来为 isUnlockSuccess 方法写一个测试。
  我们的 Project 名称是 Lab-Testing,而UnitTest文件名为Lab_Testing_UnitTest.swift。
  我们通过引入 Lab_Testing 项目(我们项目名称本来是 Lab–Testing,但似乎引入的时候只能写 Lab_Testing)来取得HomeViewController并初始化。
  @testable import Lab_Testing
  class Lab_Testing_UnitTest: XCTestCase {
      var vc: HomeViewController!
      
      override func setUp() {
          super.setUp()
          // 测试开始前会执行这里
          vc = HomeViewController(nibName: "HomeViewController", bundle: nil)
      }
  }
  接着我们针对 isUnlockSuccess 来写一段 Test,记得要以 test 开头,接着可以 comment + U 或者点 function 左侧的方块来跑测试,
  成功后方块就会变成绿色的勾勾了。
  Assert – 功能测试(2)
  还是同样的应用,但我们要加入一个功能,从新画面回来的以后,要恢复到初始状态。
  于是我们在 HomeViewController 中的 viewDidAppear 中加入 resetSettings 这个方法来初始化内容:
  将 key1Value、key2Value 还原成0。
  将 slider1、slider2 也回到初始位置(value 为0)
  将 Slider 右侧 label 的 text 也显示为 0 (初始化为0)
  并且将图片换回“未解锁”的图片。
      func resetSettings() {
          key1Value = 0
          key2Value = 0
          
          lockImageView.image = UIImage(named: "icon-lock")
          
          slider1?.setValue(Float(key1Value), animated: true)
          slider2?.setValue(Float(key2Value), animated: true)
          
          key1Label?.text = "\(key1Value)"
          key2Label?.text = "\(key2Value)"
          
          unlockSliders()
      }
  对应的测试应该这样写:
      func testResetSettings() {
          vc.resetSettings()
          
          XCTAssert(vc.key1Value == 0, "key1Value is not 0 after resetSettings")
          XCTAssert(vc.key2Value == 0, "key2Value is not 0 after resetSettings")
          
          XCTAssert(vc.key1Label.text == "0", "key1Label is not 0 after resetSettings")
          XCTAssert(vc.key2Label.text == "0", "key2Label is not 0 after resetSettings")
          
          XCTAssert(vc.lockImageView.image == UIImage(named: "icon-lock"), "image is not icon-lock after resetSettings")
      }
  我们在 Lab_Testing_UnitTest 中有先宣告:
  var vc: HomeViewController!
  并且在 func setUp() 中有初始化它:
      override func setUp() {
          super.setUp()
          vc = HomeViewController(nibName: "HomeViewController", bundle: nil)
      }
  需要特别注意的是,这样的初始化马上进行 test 会直接 crash,因为此时的 label, slider 等等,都因为他们都还没有被初始化,而单元测试也不会触发 loadView() ,所以我们需要主动去触发初始化的动作,但 Apple 却不希望我们直接调用 LoadView 方法(参考1、参考2),所以我们通过调用vc.view的方式处理了:
      override func setUp() {
          super.setUp()
          // 测试开始前会执行这里
          vc = HomeViewController(nibName: "HomeViewController", bundle: nil)
          _ = vc.view
      }
  其他的Assertions
  ●XCTAssertEqual
  ●XCTFail
  ●XCTAssertEqual
  ●XCTAssertNil /  XCTAssertNotNil
  Measure – 性能测试
  我们可以通过Measure Block来进行性能测试,比如对 testIsUnlockSuccess() 中加入性能测试:
  加入 measure 以后并跑完测试以后,右下角会显示性能测试的结果,如果点开可以看到:
  在这里通过 edit 可以设定一个 baseline 以及允许的偏差值,如果运行结果超时就会跳出错误提醒。
  Expectation – 异步测试
  一个网络请求的例子:
      func testUrlRequest() {
          let url = URL(string: "https://ios.devdon.com/")!
          let urlExpectation = expectation(description: "GET \(url)")
          
          let session = URLSession.shared
          let task = session.dataTask(with: url) { data, response, error in
              XCTAssert(data != nil, "data 不应该是 nil")
              XCTAssert(error == nil, "data 应当是 nil")
              
              if let response = response as? HTTPURLResponse,
                  let responseURL = response.url {
                  XCTAssert(responseURL.absoluteString == url.absoluteString, "URL变了")
                  XCTAssert(response.statusCode == 200, "response code 不是200")
              } else {
                  XCTFail()
              }
              
              urlExpectation.fulfill()
          }
          
          task.resume()
          
          waitForExpectations(timeout: task.originalRequest!.timeoutInterval, handler: { error in
              if let error = error {
                  print("网络请求时发生错误: \(error.localizedDescription)")
              }
              task.cancel()
          })
      }
      func testUrlRequest() {
          let url = URL(string: "https://ios.devdon.com/")!
          let urlExpectation = expectation(description: "GET \(url)")
          
          let session = URLSession.shared
          let task = session.dataTask(with: url) { data, response, error in
              XCTAssert(data != nil, "data 不应该是 nil")
              XCTAssert(error == nil, "data 应当是 nil")
              
              if let response = response as? HTTPURLResponse,
                  let responseURL = response.url {
                  XCTAssert(responseURL.absoluteString == url.absoluteString, "URL变了")
                  XCTAssert(response.statusCode == 200, "response code 不是200")
              } else {
                  XCTFail()
              }
              
              urlExpectation.fulfill()
          }
          
          task.resume()
          
          waitForExpectations(timeout: task.originalRequest!.timeoutInterval, handler: { error in
              if let error = error {
                  print("网络请求时发生错误: \(error.localizedDescription)")
              }
              task.cancel()
          })
      }
  代码覆蓋率(Code Coverage)

  XCode 近几年一直在不断的更新,测试方面也是越来越方便了,现在的XCode可以帮我们生成代码覆蓋率的资料,
  我们只需要去 Scheme -> Test -> 勾选Code Coverage。
  在跑过一轮测试以后,可以在下图中看到代码覆蓋率结果,可以看到我们其实还有很多地方没有测试到。
  相关资料可以参考官方文件。
  命令行测试(Command Line Testing)
  为了更方便地进行测试,官方提供了通过 command line 来进行测试的方法,这样我们可以通过撰写 script 来实现自动化测试
  一个简单的例子,我们指定了测试的项目、Scheme、
  xcodebuild test -project Lab-Testing.xcodeproj -scheme Lab-Testing -destination 'platform=OS X,arch=x86_64'
  有关更多命令行测试的内容,也请参考官方网站的资料。
  在了解了基本的测试方法后,我们可以搭配 Jenkins 来实现更多的自动化测试方法。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号