iOS 测试 | iOS 自动化性能采集

​今天小编跟大家分享一篇来自学院内部学员的技术分享,本文主要介绍了作者在进行 iOS 自动化性能采集的一些经验,希望对大家在进行 iOS 自动化测试时有一些启发。

不要为小事遮住视线,我们还有更大的世界

image

前言

对于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绘制性能走势图

image

如何在持续集成中使用

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269