51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 800|回复: 0
打印 上一主题 下一主题

[原创] 做好iOS单元测试其实并不简单!

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2022-10-17 15:56:53 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 草帽路飞UU 于 2022-10-17 15:59 编辑

前言



  平时写完业务代码的时候都会去自己测试一遍,后面每次有修改都需要重复测,不管是一个业务流程还是一个工具类,其实都可以通过测试框架来帮助我们完成测试,特别是一些频繁修改的代码,更需要

严谨的测试。在浅浅地对自动化测试有一些了解时,觉得写测试代码挺耗时间,但其实对后期的帮助是非常大的,可以根据自己的实际情况来决定哪些地方需要加入自动化测试。



  单元测试


  1.1 加入测试 Target

  在新建项目时,勾选Include Unit Tests和Include UI Tests,即可为项目添加单元测试和 UI 测试。

  在添加测试代码时,你需要遵守一些最基本的规则:


  所有的测试类需要继承XCTestCase


  @interface TTTestCase : XCTestCase

  测试方法命名以 test 开始


  - (void)testThatMyFunctionWorks

  用 Assertion API 进行验证是否通过


  XCTAssertEqual(value, expectedValue)


  1.2 启动测试


  单元测试的结构:

  step1:准备输入;


  step2:运行正在测试的代码;


  step3:验证输出;


  // 准备输入


  NSString *dateString = @"2000-01-01";


  // 需要测试的方法


  BOOL isToday = [TTDateFormatter isTodayWithDateString:dateString];


  // 验证输出


  XCTAssert(isToday, @"isToday false");


  以上三个部分的代码准备完成后即可开始测试,启动的方式有很多种,可以根据你的实际情况选择以下方式:


  ·代码编辑器边栏菱形按钮,测试单个用例


  · Test 导航栏,测试单个用例


  · 快捷键? + U测试全部用例


  · 使用命令行工具 xcodebuild 可以测试单个用例,也可以测试全部用例。




1.3 性能测试

  性能测试通过度量代码块执行所消耗的时间长短,来衡量是否通过测试。


  1.3.1 如何进行性能测试


  相关 API :

  measureBlock:


  - (void)testPerformanceOfMyFunction {


      [self measureBlock:^{


          // Do that thing you want to measure.


          MyFunction();


      }];


  }


  measureMetrics:automaticallyStartMeasuring:forBlock:


  - (void)testMyFunction2_WallClockTime {


      [self measureMetrics:[self class].defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{


          // Do setup work that needs to be done for every iteration but you don't want to measure before the call to -startMeasuring


          SetupSomething();


          [self startMeasuring];


          // Do that thing you want to measure.


          MyFunction();


          [self stopMeasuring];


          // Do teardown work that needs to be done for every iteration but you don't want to measure after the call to -stopMeasuring


          TeardownSomething();


      }];


  }


  1.3.2 设置基准线


  所有的性能测试需要设置一个Baseline来验证是否通过测试,没有设置的会提示No baseline average for Time。




我们可以通过点击measureBlock:方法左边菱形圆心 icon ,来设置Baseline,设置之后需要点击save保存。之后再执行测试用例时,如果成功,左边的icon会从圆心变成一个 。



1.4 异步测试

  什么时候需要使用异步测试:

  ·打开文档


  · 在后台线程中执行的服务和网络活动


  · 执行动画


  · UI 测试时




  1.4.1 异步测试 XCTestExpectation


  异步测试分为3个部分: 新建期望 、 等待期望被履行 和 履行期望 。

  · XCTestExpectation :测试期望,可以由测试类持有,也可以自己持有,自己持有测试期望时灵活性更好一些,你可以选择等待哪些期望。



  // 测试类持有的初始化方法

  XCTestExpectation *expect1 = [self expectationWithDescription"asyncTest1"];


  // 自己持有的初始化方法


  XCTestExpectation *expect2 = [[XCTestExpectation alloc] initWithDescription"asyncTest3"];


  · waitForExpectations:timeout: :等待异步的期望代码执行,根据初始化方式不同,等待的方法不同。


  // 测试类持有时的等待方法


  [self waitForExpectationsWithTimeout:10.0 handler:nil];


  // 自己持有时的等待方法


  [self waitForExpectations[expect3] timeout:10.0];


  · fulfill :履行期望,并且适当加入XCTAssertTrue等断言,来验证测试结果。


  XCTestExpectation *expect3 = [[XCTestExpectation alloc] initWithDescription
"asyncTest3"];


  [TTFakeNetworkingInstance requestWithService:apiRecordList completionHandler:^(NSDictionary *response) {


      XCTAssertTrue([response[@"code"] isEqualToString
"200"]);


      [expect3 fulfill];


  }];


  [self waitForExpectations[expect3] timeout:10.0];


  1.4.2 异步测试 XCTWaiter


  XCTWaiter是 2017 年新增的异步测试方案,可以通过代理方式来处理异常情况。

  XCTWaiter *waiter = [[XCTWaiter alloc] initWithDelegate:self];

  XCTestExpectation *expect4 = [[XCTestExpectation alloc] initWithDescription"asyncTest3"];

  [TTFakeNetworkingInstance requestWithService"product.list" completionHandler:^(NSDictionary *response) {


  XCTAssertTrue([response[@"code"] isEqualToString"200"]);


  expect4 fulfill];


  }];


  XCTWaiterResult result = [waiter waitForExpectations
[expect4] timeout:10 enforceOrder:NO];


  XCTAssert(result == XCTWaiterResultCompleted, @"failure: %ld", result);


  XCTWaiterDelegate:如果委托是XCTestCase实例,下方代理被调用时会报告为测试失败。


  // 如果有期望超时,则调用。


  - (void)waiter
XCTWaiter *)waiter didTimeoutWithUnfulfilledExpectationsNSArray<XCTestExpectation *> *)unfulfilledExpectations;



  // 当履行的期望被强制要求按顺序履行,但期望以错误的顺序被履行,则调用。

  - (void)waiterXCTWaiter *)waiter fulfillmentDidViolateOrderingConstraintsForExpectationXCTestExpectation *)expectation requiredExpectationXCTestExpectation *)requiredExpectation;


  // 当某个期望被标记为被倒置,则调用。


  - (void)waiterXCTWaiter *)waiter didFulfillInvertedExpectationXCTestExpectation *)expectation;


  // 当 waiter 在 fullfill 和超时之前被打断,则调用。


  - (void)nestedWaiterXCTWaiter *)waiter wasInterruptedByTimedOutWaiter=XCTWaiter *)outerWaiter;


  1.5 查看测试结果


  在执行测试用例后,Xcode 会返回给我们测试结果,可以通过一下途径查看:

  ·Test 导航栏


  · Issue 导航栏


  · 代码编辑器左边栏


  · Report 导航栏





除此之外,我们还可以在 Report 导航栏中查看更加详细的测试报告:

  · 测试通过/失败


  · 失败原因


  · 性能指标


  · 截屏


  · 嵌套的 activities


  · 测试覆盖率




1.6 进行单元测试

  我新建一个时间工具类,帮助我转换时间,在使用之前,我们需要先进行测试,以保证功能完整且正确。

  这个工具类有以下 4 个公共方法,


  @interface TTDateFormatter : NSDate


  + (NSString *)stringFormatWithDateNSDate *)date;


  + (NSDate *)dateFormatWithStringNSString *)dateString;


  + (BOOL)isTodayWithDateStringNSString *)dateString;


  + (NSString *)getHowLongAgoWithTimeStampNSTimeInterval)timeStamp;


  @end


  针对一个工具类的测试我们可以新建一个TTDateFormatterTests测试类,继承一个测试基类。再根据不同的方法写不同的测试方法。如果有if和switch等条件语句导致逻辑分支的代码,尽量使各个逻辑分



支都能测试到,可以配合代码覆盖率来检查哪些逻辑分支未测试。

  @interface TTDateFormatterTests : TTTestCase


  @end


  @implementation TTDateFormatterTests


  - (void)testDateFormatter {


      NSString *originDateString = @"2018-06-06 20:20:20";


      NSDate *date = [TTDateFormatter dateFormatWithStringriginDateString];


      NSString *dateString = [TTDateFormatter stringFormatWithDate:date];


      XCTAssertEqualObjects(dateString, originDateString);


  }


  - (void)testDateFormatterIsToday {


      NSString *dateString = [TTDateFormatter stringFormatWithDate:[NSDate date]];


      XCTAssertTrue([TTDateFormatter isTodayWithDateString:dateString]);


      XCTAssertFalse([TTDateFormatter isTodayWithDateString"2000-01-01"]);


  }


  - (void)testDateFormatterHowLongAgo {


  // 该方法中包含一个 switch ,要保证 switch 每个逻辑分支都测试到,所以需要多个测试。


      NSDate *now = [NSDate date];


      NSString *secAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 10 * sec];


      XCTAssertEqualObjects(secAgo, @"10秒前");



      NSString *minAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 15 * min];


      XCTAssertEqualObjects(minAgo, @"15分钟前");



      NSString *hourAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 20 * hour];


      XCTAssertEqualObjects(hourAgo, @"20小时前");


      NSString *dayAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 25 * hour];


      XCTAssertEqualObjects(dayAgo, @"1天前");


      NSString *daysAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:now.timeIntervalSince1970 - 50 * hour];


      XCTAssertEqualObjects(daysAgo, @"2天前");


      NSString *longTimeAgo = [TTDateFormatter getHowLongAgoWithTimeStamp:1544002463];


      XCTAssertEqualObjects(longTimeAgo, @"2018-12-05 17:34:23");


  }


  @end


  合理使用测试基类和测试工具类,可以避免大量重复测试代码。时间转换工具类是一个没有外部依赖的类,当一些对外部有依赖的类需要测试时,可以尝试 OCMock ,它能帮助你模拟数据。另外,当你



觉得测试框架提供的断言方法无法满足你时,也可以试着使用 OCHamcrest 。












本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-26 06:36 , Processed in 0.064256 second(s), 25 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表