iOS 测试 | iOS 自动化性能采集
<p>今天小编跟大家分享一篇来自学院内部学员的技术分享,本文主要介绍了作者在进行 iOS 自动化性能采集的一些经验,希望对大家在进行 iOS 自动化测试时有一些启发。</p><p>不要为小事遮住视线,我们还有更大的世界</p>
<p> </p>
<p><img src="https://wx4.sinaimg.cn/large/0076zEUEly4ggrpnd9f66j30zk0np761.jpg"></p>
<p> </p>
<h2>前言</h2>
<p>对于iOS总体生态是比较封闭的,相比Android没有像adb这种可以查看内存、cpu的命令.在日常做性能测试,需要借助xcode中instruments查看内存、cpu等数据.</p>
<p>但是借助instruments比较麻烦、又不能提供命令行.在持续集成中,很难时时的监控app的性能指标.并且现在app发版一般是2周左右,留给做专项测试的时间更少了,那么做核心场景性能测试,肯定是来不及的.</p>
<p>所以需要借助一些自动化工具来减轻手工采集性能指标的工作量.</p>
<h2>性能采集项</h2>
<p>app中基本性能采集项,内存、cpu、fps、电量等,因为自动化采集中手机设备是插着电脑充电的,所以不能采集电量数据.</p>
<h2>已有工具</h2>
<ul>
<li>
<p>instruments是官方提供的,不能做到自动化采集</p>
</li>
<li>
<p>腾讯gt,需要在app中集成sdk,有一定的接入成本</p>
</li>
<li>
<p>第三sdk,类似腾讯gt需要在app集成,可能会有数据泄漏风险</p>
</li>
</ul>
<h2>脚本开发</h2>
<p>上述的已有工具都不满足,在持续集成中做到自动化采集性能数据,期望的性能测试工具有一下几点:</p>
<ul>
<li>
<p>方便接入</p>
</li>
<li>
<p>可生成性能报告</p>
</li>
<li>
<p>可持续化</p>
</li>
<li>
<p>数据收集精准</p>
</li>
</ul>
<p>所以基于这几点,需要自己开发一套性能采集脚本.</p>
<h2>使用官方提供的api做性能采集</h2>
<h3>获取内存、cpu等</h3>
<pre><code>#import <mach/mach.h><br><br>/**<br> * 获取内存<br> */<br>- (NSString *)get_memory {<br> int64_t memoryUsageInByte = 0;<br> task_vm_info_data_t vmInfo;<br> mach_msg_type_number_t count = TASK_VM_INFO_COUNT;<br> kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);<br> if(kernelReturn == KERN_SUCCESS) {<br> memoryUsageInByte = (int64_t) vmInfo.phys_footprint;<br> NSLog(@"Memory in use (in bytes): %lld", memoryUsageInByte);<br> } else {<br> NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn));<br> }<br><br> double mem = memoryUsageInByte / (1024.0 * 1024.0);<br> NSString *memtostring ;<br> memtostring = ;<br><br> return memtostring;<br>}<br><br><br>/**<br> * 获取cpu<br> */<br>- (NSString *) get_cpu{<br> kern_return_t kr;<br> task_info_data_t tinfo;<br> mach_msg_type_number_t task_info_count;<br><br> task_info_count = TASK_INFO_MAX;<br> kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);<br> if (kr != KERN_SUCCESS) {<br> return [ NSString stringWithFormat: @"%f" ,-1];<br> }<br><br> task_basic_info_t basic_info;<br> thread_array_t thread_list;<br> mach_msg_type_number_t thread_count;<br><br> thread_info_data_t thinfo;<br> mach_msg_type_number_t thread_info_count;<br><br> thread_basic_info_t basic_info_th;<br> uint32_t stat_thread = 0; // Mach threads<br><br> basic_info = (task_basic_info_t)tinfo;<br><br> // get threads in the task<br> kr = task_threads(mach_task_self(), &thread_list, &thread_count);<br> if (kr != KERN_SUCCESS) {<br> return [ NSString stringWithFormat: @"%f" ,-1];<br> }<br> if (thread_count > 0)<br> stat_thread += thread_count;<br><br> long tot_sec = 0;<br> long tot_usec = 0;<br> float tot_cpu = 0;<br> int j;<br><br> for (j = 0; j < thread_count; j++)<br> {<br> thread_info_count = THREAD_INFO_MAX;<br> kr = thread_info(thread_list, THREAD_BASIC_INFO,<br> (thread_info_t)thinfo, &thread_info_count);<br> if (kr != KERN_SUCCESS) {<br> tot_cpu = -1;<br> //return -1;<br> }<br><br> basic_info_th = (thread_basic_info_t)thinfo;<br><br> if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {<br> tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;<br> tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;<br> tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;<br> }<br><br> } // for each thread<br><br> kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));<br> assert(kr == KERN_SUCCESS);<br><br> NSString *tostring = nil ;<br> tostring = [ NSString stringWithFormat: @"%.1f" ,tot_cpu];<br> NSLog (@"performance cpu:%@",tostring);<br><br> return tostring;<br>}<br><br></code></pre>
<h3>获取页面vc</h3>
<p>上边收集了内存和cpu,还需要在收集数据的同时和页面对应上.这样就清楚了是当前页面的内存和cpu情况.</p>
<pre><code>/**<br> *获取当前vc<br> */<br>- (UIViewController *) get_vc {<br> UIWindow *keyWindow = .keyWindow;<br> __weak typeof(self) weakSelf = self;<br> dispatch_async(dispatch_get_main_queue(), ^{<br> if (]) {<br> UITabBarController *tab = (UITabBarController *)keyWindow.rootViewController;<br> UINavigationController *nav = tab.childViewControllers;<br> DDContainerController *content = ;<br> weakSelf.vc = ;<br> }<br> });<br> return self.vc;<br>}<br></code></pre>
<h3>获取设备信息</h3>
<pre><code>/*<br> *获取设备名称<br> */<br>- (NSString *) get_devicesName {<br> NSString *devicesName = .name; //设备名称<br> NSLog(@"performance devicesName:%@", devicesName);<br> return devicesName;<br><br>}<br><br>/*<br> *获取系统版本<br> */<br>- (NSString *) get_systemVersion{<br> NSString *systemVersion = .systemVersion; //系统版本<br> NSLog(@"performance version:%@", systemVersion);<br> return systemVersion;<br>}<br><br>/*<br> *获取设备idf<br> */<br>- (NSString *) get_idf {<br> NSString *idf = .identifierForVendor.UUIDString;<br> NSLog(@"performance idf:%@", idf);<br> return idf;<br><br>}<br></code></pre>
<h3>数据拼接</h3>
<p>最终要把内存、cpu等数据拼接成字典的形式,方便输出查看</p>
<pre><code>输出log日志的数据格式<br><br>{<br> "cpu": "0.4",<br> "fps": "60 FPS",<br> "version": "11.2",<br> "appname": "xxxxxx",<br> "battery": "-100.0",<br> "appversion": "5.0.4",<br> "time": "2018-09-07 11:45:24",<br> "memory": "141.9",<br> "devicesName": "xxxxxx",<br> "vcClass": "DDAlreadPaidTabListVC",<br> "idf": "8863F83E-70CB-43D5-B6C7-EAB85F3A2AAD"<br>}<br><br></code></pre>
<h3>开启子线程采集</h3>
<pre><code>开一个子线程定时采集数据<br><br>/*<br> * 性能采集子线程<br> */<br><br>- (void) performancethread {<br> NSThread *thread = [ initWithBlock:^{<br> NSLog(@"performance ======get performance======");<br><br> ;<br><br> while (true) {<br> DDPerformanceModel *model = ;<br> model.time=;<br> model.appname=;<br> model.appversion=;<br> model.idf =;<br> model.devicesName =;<br> model.version = ;<br> model.vcClass = NSStringFromClass(.class);<br> model.memory = ;<br> model.battery = ;<br> model.cpu = ;<br> model.fps = self.percount;<br><br> NSString *json = ;<br><br>// printf(" getperformance %s\r\n", );<br> NSLog(@"getperformance model %@", json);<br> sleep(5);<br> }<br> }];<br> ;<br><br> NSLog(@"performance ======continue mainblock======");<br>}<br><br><br></code></pre>
<h3>初始化性能采集</h3>
<pre><code>AppDelegate.m文件中didFinishLaunchingWithOptions方法中用户各种初始化操作,可以在第一行初始化性能采集,<br>这样app启动以后就可以定时采集数据<br><br>- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {<br><br> [ performancethread];//获取性能数据<br><br> }<br></code></pre>
<h3>性能采集日志存储</h3>
<p>一般来说日志存储都是写入到本地log日志,然后读取.但是有两个问题</p>
<ul>
<li>
<p>需要读写文件代码,对于不熟悉oc的人来说比较难</p>
</li>
<li>
<p>因为是定时采集,文件IO操作频繁</p>
</li>
</ul>
<p>所以不考虑存储本地log日志的方式,可以在代码中打印出数据,通过截获当前设备运行的日志获取数据.</p>
<pre><code>模拟器可以使用xcrun simctl命令获取当前设备运行日志,<br>真机用libimobiledevice获取日志<br><br>xcrun simctl spawn booted log stream --level=debug | grep getperformance<br><br>输出log日志的数据格式,这块做了json美化,每歌几秒在控制台就打印一次<br><br>{<br> "cpu": "0.4",<br> "fps": "60 FPS",<br> "version": "11.2",<br> "appname": "xxxxxx",<br> "battery": "-100.0",<br> "appversion": "5.0.4",<br> "time": "2018-09-07 11:45:24",<br> "memory": "141.9",<br> "devicesName": "xxxxxx",<br> "vcClass": "DDAlreadPaidTabListVC",<br> "idf": "8863F83E-70CB-43D5-B6C7-EAB85F3A2AAD"<br>}<br><br>如果获取多次数据可以使用shell脚本把命令放到后台,定时写入到logpath中<br>nohup xcrun simctl spawn booted log stream --level=debug >${logpath} &<br></code></pre>
<h2>代码插入到工程中</h2>
<p>因为在持续集成中,每次打取的代码都是不带性能测试代码,这些代码是单独写到文件中.在编译项目前,用shell把代码插入到工程中,这样打出来的包才能有采集性能数据功能.</p>
<pre><code>scriptrootpath=${2}<br>AddFiles=${2}"/GetPerformance/performancefiles"<br>localDDPerformanceModelh=${scriptrootpath}"/GetPerformance/performancefiles/DDPerformanceModel.h"<br>localDDPerformanceModelm=${scriptrootpath}"/GetPerformance/performancefiles/DDPerformanceModel.m"<br>localgetperformanceh=${scriptrootpath}"/GetPerformance/performancefiles/getperformance.h"<br>localgetperformancem=${scriptrootpath}"/GetPerformance/performancefiles/getperformance.m"<br><br>addfiles(){<br><br> echo "删除${projectaddpath}中的原性能采集文件"<br><br> rm -rf ${DDPerformanceModelh}<br> rm -rf ${DDPerformanceModelm}<br> rm -rf ${getperformanceh}<br> rm -rf ${getperformancem}<br><br> echo "复制文件到${projectaddpath}路径"<br><br> cp ${localDDPerformanceModelh} ${projectaddpath}<br> cp ${localDDPerformanceModelm} ${projectaddpath}<br> cp ${localgetperformanceh} ${projectaddpath}<br> cp ${localgetperformancem} ${projectaddpath}<br><br>}<br></code></pre>
<h2>性能数据绘制</h2>
<p>在手工和自动化使用插入性能测试代码的app,如果截获性能数据后,可以对数据做性能数据绘制.</p>
<p>用Higcharts或者echarts绘制性能走势图</p>
<p><img src="https://wx4.sinaimg.cn/large/0076zEUEly4ggrpnd99afj31cg0u0grp.jpg"></p>
<h2>如何在持续集成中使用</h2>
<p>monkey和UI自动化中使用,最终会发送一份性能报告.</p>
<h2>Demo代码</h2>
<p>已经把性能代码脱了主项目,可在Demo代码中编译,github地址:https://github.com/xinxi1990/iOSPerformanceTest</p>
<h2>最后</h2>
<p>虽然iOS生态封闭,但是对于开发者和测试者还是有一些空间可以利用的.</p>
<p>iOS测试一直都是一个难点,难懂的oc语法和iOS整体框架.如果你开始慢慢接触iOS,会发现iOS测试也并不是那么难,需要一点耐心和一点专心而已.</p>
<p> </p>
<p>(文章来源于霍格沃兹测试学院)</p>
<p>更多优秀内容及资料可点击获取</p>
<p>http://qrcode.testing-studio.com/f?from=bokeyuan&url=https://ceshiren.com/t/topic/3595</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/hogwarts/p/13306336.html
頁:
[1]