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