iOS 测试 ios SDK 接口测试 TIPs 分享——个人感悟、异步函数测试等

匿名 · 2017年02月12日 · 最后由 水墨 回复于 2019年05月31日 · 4743 次阅读
本帖已被设为精华帖!

一、背景

目前市场上有很多集成了各种功能的 SDK,开发者只需要调用 SDK 中提供的 API 就能实现一些原本实现起来比较复杂的功能,不需要关心怎样去跟 server 通信,也不用搭建自己的后端 server。我认为 SDK 这个东西秉承了 “让专业的人去做专业的事” 的精神,对开发者来说在某种程度上提供了很大的便利。

最近研究了下 iOS SDK 的接口测试,进行了总结。如有不对的地方,望不吝指正呀~

二、从客户端到 SDK 接口测试的几点感悟

  1. 要知道你要测试的到底是什么。
    之前一直是通过 IM 类客户端来测试 SDK 的接口调用,很多功能的执行结果判断起来非常直观。举个例子:在测试发送 1 条消息给某人,从客户端的角度很容易判断这个 case 通过没有,只要看看界面有没有对应的消息就可以了。但是当我从 sdk 接口测试的角度去判断时,一下子有点蒙蔽了,完全不知道怎么判断这个 case 是否通过(请原谅那时我还是个 OC 小白)。 针对上面那个问题,可以将其拆分成两步:step1:用对应的开发语言去实现 “发送消息” 这个过程。step2:判断消息是否发送成功。第一步通过学习 OC 的基本知识就可以实现,而第二步需要了解一些客户端的开发常识以及 SDK 的使用方法,自己随后补充了下这方面的知识,了解到这个过程是这样的,如下图所示:
    客户端调用了 “发送消息这个接口”,将该条消息发给服务器;服务器收到后进行处理,处理成功后给客户端一个回包;这个回包触发了客户端的 “消息是否成功发送” 这个回调函数,客户端在这个回调中解析服务器的回包,例如解析出返回码是成功的,就在界面 UI 把发送的这条消息渲染出来,至此完成了整个过程。所以在这个 case 中我们需要关注,在收到服务器的回包后,相应的回调是否被触发,且服务器返回的回包数据是否与你期望的一致。
    客户端的很多功能的实现大部分都是这种方式(当然图中是最简单的方式,还有复杂的情况,例如多回调触发,回调多次被触发等),因此要做好 SDK 接口测试,需要准备相应开发语言的知识,并且了解 SDK 的的使用方法,所以自己尝试去用 SDK 开发一个 demo 是最好不过了!
  2. 设计测试用例的思路转变。
    客户端的测试用例很多时候从上至下考虑的,即主要从用户的使用层面去设计测试用例,一开始我在设计接口测试用例的时候也是通过写代码构造各种场景来判断接口调用是否正确,但是随后发现忽略了接口本身,而且应该去了解一下接口的内部代码,针对接口的实现去设计一些 case(这块我也在学习中,先把观点抛出来,[二哈]),例如参数检查等等,换句话说就是测试用例的设计加入从下到上的部分。同时最近读了论坛里@chenhengjie123接口测试的一些感悟 受益匪浅,大家可以读下,再此就不再赘述。说实话感觉 SDK 接口测试在某种程度上跟服务器的接口测试有很多值得借鉴的地方(原谅我是个服务器接口测试的小菜鸟)~
  3. 接口测试并非万能的。
    接口测试在很大程度上能够解放我们的双手,减轻手动测试的负担,但是一些测试还是需要借助手工测试来完成,比如一些异常情况,弱网下的表现,网络切换等等,所以我们要做的是:能够用 sdk 接口测试完成的,我们坚决不动手,但也不能过分依赖 SDK 接口测试,而忽略了全面的测试指标。

三、测试 Tips

3.1 异步测试方法

异步方法不像 require 方法那么容易上手,在文章最上面的那张图示中,当服务器会包返回给 app 时,被触发的回调就是异步函数,这个函数不会阻塞线程,而且什么时候被触发完全取决于服务器回包的时间间隔,所以当我们测试该类的方法时,需要满足这样的场景:当我们的回调函数被触发时,测试用例的代码才会结束执行。测试异步函数的方法也有很多,我这里采用的是给予 xctest 框架提供的 ‘expectationForNotification’ 和 ‘waitForExpectationsWithTimeout’ 函数以及 ios 的通知中心功能的一些函数来实现的,见如下代码:

- (void)testSendtextmessage{
    NSString *text=@"文本消息";
    //构造文本消息
    NIMSession *session=[NIMSession session:_advanceTeamWithSendMessage type:           NIMSessionTypeTeam];
    NIMMessage *message=[[NIMMessage alloc] init];
    message.text=text;

   //调用发送消息的API
    [_chatmanager sendMessage:message toSession:session error:nil];

   //监听ios通知中心是否有内容为'SendMassageSuccess'通知消息
    [self expectationForNotification:@"SendMassageSuccess" object:nil handler:nil];

  //监听的行为最多持续60s,60s之内收到了该条通知消息,测试执行直接结束。60s之内没有受到则说明有问题
    [self waitForExpectationsWithTimeout:60 handler:nil];
}
//异步回调函数
- (void)sendMessage:(NIMMessage *)message didCompleteWithError:(NSError *)error
{
    //此处可以添加一些对服务器回包数据的判断代码
    //向ios通知中心发送内容为'SendMassageSuccess'通知消息
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SendMassageSuccess" object:nil];
}

如代码中的注释所解释的那样,我们在异步函数中做了一件事情,就是向 ios 通知中心的发送消息,而在测试代码的 case 中去针对这个通知消息做异步等待,并设置一个最大的超时时间,以此来实现判断异步函数是否被正确的触发以及服务器数据回包是否正确。

3.2 巧用 random

作为一个 tester,我们大量的工作就是测试各种不同的场景结果是否符合预期,一套参数组合就是一个测试用例。转换到 SDK 的接口测试,我们可以用一种比较巧妙的办法,避免一种参数组合写一个测试 case,而是通过循环与 random 的结合,达到同样的效果。

举个例子我在测试中,有一个 “创建高级群” 的接口,这个接口有 2 个参数(在此只拿 2 个参数为例,实际情况下更多):参数 1-申请入群的权限(枚举值 0-直接申请即可加入,1-申请后需管理员同意才可加入),参数 2-修改群资料的权限(枚举值 0-仅群主可修改,1-群主和管理员可修改,2-所有人均可修改)。这样这个接口就有 2X3=6 中参数的组合情况,最直接最暴力的方法是写 6 个结构类似的测试 case,保障这个接口是 OK 的,但是这样会造成代码很多很冗余,正如题目所述,可以利用 random 这个特性来搞些事情。如下图所示:

- (void)testCreateAdvancedTeam
{
    NSString *userId = [[[NIMSDK sharedSDK] loginManager] currentAccount];
    NSArray *users = @[userId];

    NSMutableArray *teamIds = [NSMutableArray array];
    //利用循环+random构造case
    for (NSInteger index = 0; index < 6; index++)
    {
        //构建群时所需要的参数
        NIMCreateTeamOption *option = [[NIMCreateTeamOption alloc] init];
        //申请入群的权限的参数在0和1之间随机产生
        option.joinMode = arc4random() % 2;
        //修改群资料权限的参数在0、1、2之间随机产生
        option.updateInfoMode = arc4random() % 3;
        //调用创见高级群的API
        [[[NIMSDK sharedSDK] teamManager] createTeam:option
                                               users:users
                                          completion:^(NSError *error, NSString *teamId) {                                             
                                           //一些对返回结果的判断代码
                                          }];   
    }
}

上述代码就是通过做循环+每次随机产生随机参数的模式来减少代码量,从严格意义来讲并不是安全严谨的,但是若参数组合有 12 种情况,其中一个参数每次都没被随机到的概率为 1/12,而且我们的测试工程代码并不是仅仅跑一次,可能会跑很多次,这样的话某个参数不被覆盖的的概率会很小很小,数学的力量!!

3.3 公共类

在编写测试用例的时候,有些场景是多次重复使用的,比如发送一个图片消息等等,我们可以把构造一条图片消息封装成一个公共类,而不要傻傻的每用一次就代码编写一次生成图片的代码。随着测试用例的丰富,你的测试工程中应该有各种各样的公共类,方便你实现各种各样的测试场景。所以,随着你的测试用例越来越多,你的测试公共类也应该越来越丰富。

3.4、测试代码覆盖率

当测试代码有一定数量的时候,这时候你就得考虑一个问题,我写的测试代码到底 “好” 还是 “不好”。这里不能简单地用测试用例的个数或者测试代码的数量来衡量,因为很有可能你的测试用例有大量的重复测试,比较科学的方法是统计测试工程的代码覆盖率。关于如何统计代码覆盖率,苹果的官方文档(后文参考文献会给出)中专门有一个章节'code coverage'说明,这里就不详细讲了,各位看官自己去了解下吧:D。

四、参考文献

共收到 15 条回复 时间 点赞
匿名 #1 · 2017年02月12日

对于巧用 random,我的个人觉得用作功能测试心慌慌誒。测试的首要目标还是要覆盖到用例吧。在覆盖到用例的前提下去优化代码。如果用例组合并不多,用嵌套循环保证用例都覆盖到比较靠谱😉

匿名 #2 · 2017年02月12日

#1 楼 @MinnaCheng 嗯嗯,说得对。这块应该是我欠考虑了,过于去追求代码的简洁性而忽略了测试的根本目的,这块还是建议用循环嵌套来首先保证覆盖率。如果参数非常多,且测试代码运行较频繁可以考虑用这种 random 的方式。

谢谢分享,竟然被 @ 到了,受宠若惊啊。

我现在也没做过 sdk 测试,公司目前没有 sdk 型的项目,不过后面可能有写测试辅助用 sdk 的需要,到时候可以应用文中的技巧~

  1. 用例设计,看调用了这个 API 以后的返回值就可以了啊?因为 UI 上显示的内容,就是引用这个 API 的返回值。
  2. JUnit 里面有参数化测试,就是测试一套组合参数,XCTest 可能也有,反正我没找到😂
  3. 我觉得如果单纯的从测试 SDK 的角度出发,不应该牵连到具体的 Server,因为有可能 Server 那边本身就会有问题。这个时候,可以用 OCMock,服务器那一端,用 OCMock 来模拟。
  4. 异步这一块,是我目前没做的,然而大部分问题,其实都出现这里。我一直被项目赶着走,都没时间好好想想这个问题。。

最后,我在你的代码里为什么没有看到断言呢,难道我的打开方式不对😂

匿名 #5 · 2017年02月13日

#4 楼 @da_sheng 2.在 xctest 里我也没有找到相关参数化测试,这块的确也可以从这个角度出发找找有没有什么插件之类的,提供更严谨更方便的的参数组合排列
3.ocmock 这块也是下一步要需要了解的,主要目前我们 SDK 接口测试的代码才开始,还远没跟上项目的接口数量,所以现在的接口测试代码都是补之前的缺漏,用于回归。后续新功能测试时,可能需要考虑 mock,不过我觉得牵连到具体的 server 也不错,算是对项目测试的双保险。
断言代码我没附上去。。。。主要是一些返回码以及返回数据的判断,我在代码段里直接用 “//一些对返回结果的判断代码”、//此处可以添加一些对服务器回包数据的判断代码 “” 这些话替代了,没拷贝上去😂 😂 谢谢指教~😜

匿名 #6 · 2017年02月13日

#3 楼 @chenhengjie123 哈哈~共同进步~

我有个问题,这种测试方法需要把重打包植入 SDK 吗?如果是发布版本会不会还带上这个 SDK?

恒温 怎么做 SDK 的兼容性测试 中提及了此贴 02月13日 16:45
匿名 #9 · 2017年02月13日

#7 楼 @0x88
目前我所知的有两种方法
(1)建一个空白的 app 工程,植入 SDK(开发源码打包好的 SDK 文件),在这个工程里增加 XCtest 的 target,编写测试用例,开发代码有更新的话,就生成新的 SDK,导入到你创建的的 app 工程。优点是不依赖开发源码。缺点是:无法统计代码覆盖率。
(2)在开发的源码工程上,增加 Xctest target 编写测试代码。优点是进一步接近开发的代码,可以统计测试代码覆盖率。缺点是:开发很难同意你在他们代码仓库里直接添加代码,毕竟测试的代码水平是个未知数,同时增加了污染代码的风险。
我们开发也有上述的担忧,但是我这边还是采取了第二种方法,只不过我自己建了个仓库,把开发的源码挪到我自己仓库里,再增加测试代码,然后定期更新自己仓库里的开发的源码。
后面一句没理解什么意思,这跟发布版本啥关系啊?我们发布的产品就是这个 SDK~

#9 楼 @wowotou 那我明白了。确实第二种方法是有安全风险。

恒温 将本帖设为了精华贴 02月14日 08:42

文章和评论区都很不错。期待更多的 sdk 测试分享。

匿名 #13 · 2017年02月14日

#12 楼 @Lihuazhang 感谢组织~再接再厉~

#2 楼 @wowotou 在参数很多的情况下,用 random 的方法随机减少测试用例,倒不如采用 pairwise 的方法去更科学的设计一些用例。这样既避免了爆炸式增长的全量 case,相对于 random 来说可能在 case 覆盖方面更合理。

匿名 #15 · 2017年02月16日

#14 楼 @willys 好,去了解下 pairwise 大法,经过大家指教,感觉我这个 random 方法的确有些鸡肋~😂

#15 楼 @wowotou 哈哈,谢谢你的分享。恰好最近在测 ios sdk。多多交流。

请教一下,sdk 的释放资源接口有必要测试吗,这个怎么测试呢?刚刚开始做 sdk 接口测试,不太了解。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册