京东多端全流程交易解决方案阿波罗平台iOS单元测试实践

Posted 京东零售技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了京东多端全流程交易解决方案阿波罗平台iOS单元测试实践相关的知识,希望对你有一定的参考价值。

“我非常确信,在我有生之年,对软件发展的最大贡献不是来自面向对象方法和高级语言、函数式编程、强类型、MVC 或其他任何东西,而是来自测试文化的兴起。”
——XML 之父 Tim Bray







单元测试

什么是单元测试?

单元测试,是针对一小段代码或方法,检验被测代码一个小的、明确的功能是否正确 ,证明某段代码的行为和开发者期望一致的行为。简单来说,是针对单个程序、函数、过程进行正确性检验的工作的白盒测试。
单元测试应该遵循F.I.R.S.T原则:
快速(Fast):测试应该能快速运行。
独立(Independent)测试应该相互独立,不能有依赖。
可重复(Repeatable)测试应当可在任何环境重复通过。包括无网络的环境。
自足验证(Self-Validating)测试应该有布尔值输出。
及时(Timely)测试应及时编写。单元测试应该恰好在使其通过的代码之前编写。
    
前端是否需要进行单元测试?

一个成熟的软件开发人员非常清楚测试的重要性。当我们需要开发一个新功能、新需求时,我们通过单元测试来验证一段代码中是否运行的正常,传入极端边界值是否会出现问题,单元测试还能使第一次阅读代码的开发人员更好的理解这段代码的含义。
经过良好的的单元测试过的代码总会给开发人员带来勇气。当我们需要重新修改需求、甚至重构代码时,因为有了良好的单元测试,即使你搞砸了,你也会很快通过运行不通过的单元测试发现问题。
所以单元测试,前端和后端都很重要,现在和以后都很重要。
    
单元测试现在还存在哪些问题?

开发成本增加。TDD原则要求我们在开发代码前先编写单元测试,按照这条规则,我们编写的单元测试将覆盖所有业务代码。测试的代码量足以匹敌需求代码量,编写单元测试所需要花费的时间将和开发需求的时间相同甚至远超开发时间,这无疑增加了开发人员的开发成本。
   
无法验证单元测试的有效性。基于控制唯一变量的原则,如果要验证单元测试的有效性,我们需要找到两个强大的团队来执行重要的开发任务,并且在规模、结构、工具、技能水平和工作实践——在除测试之外的所有方面的表现都大致相同。然后,还需要在一个比较长的周期内研究他们的生产力和质量差异。无疑还没有团队能够这样去做,所以我们无法使用有效的数据来说明单元测试的有效性,只能凭借以往开发者的经验积累来进行单元测试。

接入单元测试方案,我们要如何选择?

在阿波罗早期调研时,公司内部还没有太多的方案可供选择。所以我们主要是使用手动导入XCodeCoverage和OCMock库再接入Bamboo的方案。
    
现在iBiu最新版本已经支持了单元测试,内部也是使用XCodeCoverage库来生成覆盖率。

    
那么就先针对这两个平台对现有的单测支持能力做一个对比:


Bamboo的定时触发

方案选择建议:
1. 如果你现在已经接入了iBiu,但是现在不是太着急生成视图的单测覆盖率报告,可以先使用Xcode的自带单测覆盖率查看,等iBiu与Bamboo数据打通后直接通过iBiu接入。(两个团队已经在紧急建设中)

2. 如果部门需要统一在Bamboo中统计单侧覆盖率,那么可以直接使用手动接入Bamboo方式,除了第一次配置略麻烦,后面直接在Bamboo定时运行还是很方便。
    
3. 如果你的工程没有接入iBiu,或者还不想接入Bamboo,但是想要生成更加详细的单测覆盖率,可以只接入XCodeCoverage和OCMock,生成本地单测详细报告。
    
iBiu接入和Bamboo中生成单测覆盖率报告都是使用XCodeCoverage,下文会详细介绍这个库的使用方法。


单元测试覆盖率

无论在iBiu、还是Bamboo,我们关注的有效数据都是单元测试覆盖率。
    
如何使用Xcode生成覆盖率数据?
    
无论是哪种方式接入,单测覆盖率最终的数据来源都是通过Xcode生成。

步骤如下:
1. 在工程中新增单测的Target:


2. 开启覆盖率需要覆盖的范围,选择你需要测试target,一般是我们的组件源码所在的target;


3. 修改目标target的Settings



4. 写好单测代码,运行command+U后,就可以看到生成的单测覆盖率


5. 点开详细内容,可看到具体哪些代码被覆盖,其中红色代表没有被覆盖,绿色代码已经被覆盖。

Xcode生成的单测覆盖率报告

点击方法后面的箭头会跳转到对应的代码中

当每一次运行单测后,都应该快速的点开浏览所有重要的代码块,查看被覆盖和没被覆盖的部分。往往有些你觉得单元测试已经被覆盖到的部分,其实并没有覆盖到。
    
一般来说,单测覆盖率不会达到100%。阿波罗前端本次接入单测只考虑业务逻辑的代码部分,UI单测并不在本次的规划当中,所以我们的单测覆盖率对比服务端相对会更低一些。
   
如何得到更加详细的单测覆盖率报告?
XcodeCoverage提供了一种简单的方法来生成Xcode项目的Objective-C代码覆盖率报告。生成的报告包括html和XML。覆盖数据不会包括苹果sdk,并且XcodeCoverage现在还不支持Swift。

1. 安装XcodeCoverage

  • 如果工程是使用iBiu,那么在Podfile文件平级的文件夹中,新增一个Podfile.custom文件,然后使用iBiu进行安装。

  • 文件内容:

       
  • 如果工程没有使用iBiu,那就使用CocoaPods正常安装,在Podfile文件中增加pod \'XcodeCoverage\',然后pod install。

  • 另外还需要在终端安装两个工具:
    gem install xcpretty:
    gem install ocunit2junit
    xcpretty用于对xcodebuild的输出进行格式化。并包含输出report功能。
    ocunit2junit用来将 OCUnit 的输出转换成 JUnit 风格的报告
        
    2. 配置XcodeCoverage

    在你的主工程中加一个Run Script,运行一个脚本文件
  • $PODS_ROOT/XcodeCoverage/exportenv.sh

        
    3. exportenv.sh脚本
    打开一下要运行的脚本,文件就在Pod/XcodeCoverage/下


    主要作用是新生成了一个env.sh文件,并写入了BUILT_PRODUCTS_DIR、CURRENT_ARCHOBJECT_FILE_DIR_normal、OBJROOT、OBJROOT等参数。

    然后我们先command+b或者command+u一下,运行这个脚本去生成env.sh。

    4. env.sh脚本

    打开生成的env.sh脚本看一下


    env.sh脚本中有几个参数需要我们关注:

  • OBJECT_FILE_DIR_normal,这个值默认生成的是我们主工程的地址,但是现在我们要测试的是包含业务代码的target,所以这个值需要修改为目标target的地址。


  • 我们真正的代码其实在工程中是相当于一个pod,所以这个值需要改成Pod所在的位置/Users/xxxx /Library/Developer/Xcode/DerivedData/xxxCartModule-gtprkfazpqerpsczqxftrlwkzauw/Build/Intermediates.noindex/Pods.build/Debug-iphonesimulator/xxxCartModule.build/Objects-normal。
    主要修改是标红的两个位置。
      
  • SRCROOT,可以看到默认生成的值是指向Example文件夹的,也就是我们一般写调用、验证组件的等的测试代码的地方,而我们实际的需要被单测的代码都在classes文件中,所以这个值需要改成:/xxxCartModule/xxxCartModule/Classes。

  •     
    需要注意,因为env.sh是由于run exportenv脚本每次动态生成,就会导致每次build或者跑单测都会生成一次,都需要改一次上述的两个值。如果觉得比较麻烦可以写一个脚本进行修改。
    修改完成后我们cd到Pods/XcodeCoverage文件夹,执行getcov脚本。/getcov -s,运行后,浏览器会自动打开生成的单测覆盖率详细报告。
          
    5. 覆盖率报告

    生成的报告包含总代码行、已覆盖的代码行、代码覆盖率、总方法数、已覆盖的方法数、方法覆盖率,可以点击每一个类查看覆盖的代码和没有覆盖的代码。如果想要将单测报告生成到指定文件夹,可以执行

     ./getcov  -o  你的文件夹

        生成的测报告
        
    文件中的详细代码覆盖情况
         
    6. 覆盖率报告生成原理

    Xcode编译后会为每个可执行文件生成对应的 .gcno 文件;之后在代码中调用覆盖率分发函数,会生成对应的 .gcda 文件。其中,.gcno 包含了代码计数器和源码的映射关系, .gcda 记录了每段代码具体的执行次数。覆盖率解析工具都需要结合这两个文件给出最后的检测报表。

    这就是为什么我们需要修改env.sh中的文件路径,因为需要让XcodeCoverage去扫描正确的gcda 文件。

    刚刚在终端运行生成单测覆盖率时的输出记录可以验证以上内容。


    7. 可能遇到的报错问题
    在生成覆盖率报告的日志中,我们还会发现有类似这样的输出:


    像我的工程输出这个报错是因为在代码中使用了外部类的一个方法。


    一般我们工程中会引用到许多JD内部库,除了上面的运行错误日志外,还会导致无法生成最后的单测报告文件:


    解决办法是:需要让XcodeCoverage在扫描文件的时候忽略掉JD库,在Classes下新增一个.xcodecoverageignore文件,文件内容中写要忽略的库就可以了。

    $SRCROOT/报错的库 /*

    8. 关于getcov的其他用法
  •  `--show` or `-s`: Show HTML report. 
  • `--xml` or `-x`: Generate Cobertura XML. 生成覆盖率XML
  • `-o output_dir`: Specify output directory. 将结果输出到一个文件夹
  • `-i info_file`: Specify name of generated lcov info file. 指定生成的lcov信息文件的名称
  •  `-v`: Enable verbose output.
  • `-h` or `--help`: Show usage.

  • 当通过以上步骤本地工程可以正常生成覆盖率报告文件时,接入Bamboo就变得非常容易。


    开发单元测试

    开发测试代码和开发需求代码一样重要。测试必须跟随业务代码的修改而修改,它不是一次性的产物,也需要开发人员进行维护。

    哪些代码需要写单元测试?

    下面表格列举出一个工程中主要部分和是否需要单元测试,供大家参考:


    写单元测试应该在代码开发之前,且应该自下向上进行,从最基础的开始写

    如何写一个单元测试?

    单元测试开发主要是基于以下三个要素:
    Given:配置测试的初始状态,比如造一些数据、mock一些对象等,这里我们可能需要OCMock这个库的辅助。
    When:对要测试的目标执行代码,调用你要测试的方法
    Then:对测试结果进行断言(成功 or 失败),对于结果做一个预判,并且通过编译来判断你的预判是否正确。


    关于单元测试开发规范,我们的建议是:
    一个单测类只测一个代码类。一个单测类中只测试这个类的相关方法,不要把所有的单测方法都写在一个测试类中。这样看上去拥挤不堪,既不美观又不好维护。并且注意命名规则,可以是被测试的类名+Test。
        
    一个单测方法只测一个方法的一种情况。我们不要想写一个超长的测试方法,在同一个方法里测试完这个又测试那个,就像下面这种,需要拆分成两个单测方法。当然一个方法中可以针对一种情况写多条断言。
        
    ×不要这样写↑

    如何测试代码中的私有方法/私有属性?

    如果你需要测试定义在.m中的一个方法或者一个属性。可以在单测的target中,创建一个Category,将私有的方法和属性开放到.h中。并且可以将Category的.m文件删除,它并没有什么用。


    单元测试中如何mock数据?

    写单元测试的大部分时候我们都需要mock数据
    举个例子,业务代码如下,单元测试需要验证是否走到了if里:

    业务代码

    方式1:模拟一份假数据当做入参


    方式2:你当然可以不关心假数据是什么,只关注这个方法中的逻辑。那就使用OCMock直接存根一份对象。


    具体选择哪种方式可以根据被测试的方法自行判断:如果入参是一个对象,那么选择OCMock;如果入参是一个基础数据,造假数据比较麻烦可以选择OCMock,假数据结构不麻烦的话可以直接mock假数据。

    单元测试如何做断言?

    需要特别关注断言失败的单测,这可能证明你对你的代码有某些误判。

    苹果提供了断言XCTAssert等方法,具体如下:


    从以上表格中可以看到苹果提供的基本是对变量的判断。
        
    当我们需要校验的是最后是否跳转到另一个方法时,就需要使用OCMock的OCMVerify方法。
        
    如下图举例:

    业务代码

    单测代码

    最后让我们聊一聊OCMock

    OCMock实际是利用runtime原理,通过消息转发方式来实现的。感兴趣的同学可打开源代码看一看。
        
    OCMock提供了下面这些方法:

        


    OCMock在使用上有以下几点需要注意:

    1. OCMock生成的对象是id类型,在使用mock对象的时候,调用属性名和方法名都不会自动带出,所以调用的方法名需要自己写出来。

    2. 在一个指定类上,只能有一个mock对象

    *不要这样写↑
        
    3. 在使用断言方法判断一个方法时,如果方法有入参,可以使用[OCMArg any]当做入参,该方法可以返回一个id类型的参数,这样无需纠结入参的值,需要注意int、float等类型不可以使用。如果在断言方法时代码明明走到了单测依旧报错,可以校验下方法中的传参是否相同。


    4. 使用OCMock时,最好在每次使用完后手动调用stopMocking,养成用完即释放的好习惯


    以上就是OCMock经常使用的方法,建议在使用时看看官方文档《OCMock》
    关于OCMock的实际用法还可以参考《OCMockTests》。


    阿波罗平台


    阿波罗平台致力于提供多端、全流程的交易解决方案,帮助用户快速搭建交易类的APP和小程序,并且具备一定的组件配置和共建能力,满足业务需求。

    内部员工详细资料请在神灯查阅。



    京东金融App端链路服务端全链路压测策略

    京东金融移动端全链路压测历时三个月,测试和服务端同学经过无数日日夜夜,通宵达旦,终于完成了移动端链路的测试任务。整个测试有部分涉及到公司敏感数据,本文只对策略部分进行论述。

    1.系统架构与策略

    在聊性能测试之前,简单的对金融系统架构进行简单的梳理。京东金融App架构较为复杂,为了说明问题对架构进行简化和抽象。

    金融App客户端主要是通过原生主框架和运营平台(乐高)配置搭建组成App客户端;主框架和运营平台(乐高)通过调用网关接口连接各个业务系统。实现整个业务正常运转。金融App移动端618专项测试包含App客户端专项测试和App链路服务端性能两部分内容,本文主要对App链路服务端性能进行简单说明。

    京东金融App业务模拟示意图

    根据架构特点和业务特点,将金融移动App链路服务端性能测试。共分为三个阶段,服务端基础能力测试、服务端相关业务链路测试、服务端全链路预演等三个阶段。

    2.测试方案及实施要点

    通过对移动端业务的特点和架构综合分析,将移动端链路分为三个阶段进行测试,每个测试阶段侧重点和目标不同,通过分阶段实施,一步步测试和验证金融App链路是否能够完成并满足618业务要求。

    在本次618备战服务端测试主要分三个阶段,第一阶段主要进行服务端能力和故障模拟;第二阶段主要进行业务能力测试和业务链路性能测试。第三阶段主要进行全链路压测,模拟线上用户在高并发下服务端各业务的表现及业务升降级演练。

    1)服务端能力及服务故障模拟阶段

    服务端第一轮性能测试,涉及核心业务网关和乐高基础能力性能测试。

    通过模拟正常业务、业务超时、应答错误,业务方无响应、业务数据包超大,业务数据包丢失,业务数据包不完整、接口限流等业务能力。DB不可用、连接数占满、硬盘,应用服务器硬盘沾满、应用服务器cpu过高、内存过高等系统资源问题,以及乐高或网管系统扩容和缩容测试。

    通过模拟各种异常情况验证系统基础能力是否满足高峰期间业务流量。

    京东金融App端链路服务端全链路压测策略

    基础能力测试

    第一阶段性能测试难度较大,一则是因为基础能力测试和传统业务测试在思考方式上有较大差异;另外基础能力测试需要模拟各种异常情况,需要高度抽象各种业务情况,需要编写各种模拟代码,对传统测试能力要求较高。

    2)基础能力业务测试和业务链路性能测试

    服务端第二轮性能测试,包含两部分内容,一部分主要是对第一阶段测试基础能力(乐高、网关)系统接入真实的业务进行业务性能测试。在接入业务时测试时,网关系统接入下游业务策略是选择高峰时期top30的业务接口进行进行测试。乐高系统通过线上流量复制,按照线上调用业务模板的比例进行等比配置,覆盖所有模板实例,确保趋近于模拟线上真实业务模板实例和后台接口测试乐高系统。

    在选择接入下游系统数据和接口时,选择的策略不同,测试的结果差异较大,所以采用什么样的选择策略就显得尤为重要。

    另外一部分是App基础业务、高频和关键业务性能测试,这部分主要通过对单业务或者单业务链路的测试,验证该业务链路是否满足系统要求。这部分和大部分公司日常的性能测试方案和方法一致。在此不再赘述。

    另外在此阶段有一个非常重要测试演练,不断要测试集群的性能,还需要进行单机的性能,根据扩容行测试,评估和预测扩容机器。

    3)测试服务端全链路预演

    基于前面两个阶段对基础能力性能测试和基础业务、高频业务、基础业务、活动等业务的性能测试和评估,各业务根据618移动端链路流量预估,形成整体移动端链路压测方案。关于全链路压测网上的方案非常之多,本文不在赘述。

    在第三个阶段,除了验证业务支撑能力,能不能满足预估流量;还需要重点关注高峰时段流量对App业务影响,并根据压测情况对业务实时升降级处理。如果超过预估流量或者发生意外时,那些业务可以进行降级,如果降级,会不会影响到其他业务等等。

    此阶段重要的一个任务就是演练,模拟演练618洪峰流量对业务对App的影响,性能测试需要测试和评估出每个业务升降级的临界数据,配合开发和运维同学在测试过程中进行故障模拟和演练。

    3.总结

    全链路压测和平常压测的一个很重要的区别是,全链路压测是证明容量规划的准确,流量控制策略得当。流量控制策略最核心的可以做到限流分流降级,限流分流降级说起来很容易,但需要开发、测试同学在前期做好大量工作,业务是否做到解耦和具备升降级能力,测试同学是否通过测试准确的验证容量规划的合理性,业务升降级的临界值是否合理得当等等。

    4.感谢

    整个任务完成之时,还害怕哪块没准备好,有点担心。但在6月1号写完此文,内心无比坚定的认为这次备战肯定是成功的。写此文一则是为了总结经验,二则是为了感谢为此次备战准备了三个月身边的小伙伴。

    感谢为保障这次测试任务的所有移动端测试同学,在那么短的时间,那么少的人手,完成了几乎是平常工作量2倍的工作,你们是最棒的,感谢你们。

    感谢移动端开发,帮忙一块梳理业务,每个边边角角都帮我们补充到。喜欢你们认真的样子。

    感谢服务端的同学,不厌其烦的配合我们一次次调试差问题,和我们一起加班,一起看星星,一起看日出,一起悲伤,一起欢乐。

    当然必须再此感谢所有参与这次移动端链路功能专项测试,客户端专项性能测试,服务端的同学。

    写在后面的话:完美的遗憾

    整体来说本次移动端链路备战非常成功,但有个小小的遗憾,在618当天晚上八点活动中因为瞬间业务(5秒)访问新高,触发熔断机制,导致业务失败率较高。出现瞬间访问过高的原因是因为活动结束后,用户瞬间返回主页面,导致主页面业务访问量过高。

    建议后期在业务设计时一定要考虑业务完成的情况,尽可能建立多层级的业务分流机制。避免业务完成时的瞬间访问量发生。同时也要建立自动降级的策略,防止业务瞬间访问量上升导致的降级策略失效的问题。

    长按关注V社北京

    测试技术面试DevOps

    关注V社北京,关注测试,添加巨蜥小程序获取全量精品技术文章

    关注我

    每天进步一点点




    以上是关于京东多端全流程交易解决方案阿波罗平台iOS单元测试实践的主要内容,如果未能解决你的问题,请参考以下文章

    uni-app 1.2发布,iOSAndroid小程序H5主流四端全覆盖

    JDFlutter | 京东技术中台新一代跨平台开发框架

    TiD精彩回顾| 京东商城代码质量平台建设实践

    直播预告丨京东到家支付平台的高可用架构设计实践

    京东618:商城交易平台的高可用架构之路

    解密京东618技术:重构多中心交易平台 11000个Docker支撑