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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
性能测试 PTS,5000VUM额度
简介: 前言对于iOS总体生态是比较封闭的,相比Android没有像adb这种可以查看内存、cpu的命令.在日常做性能测试,需要借助xcode中instruments查看内存、cpu等数据.

image.png
前言
对于iOS总体生态是比较封闭的,相比Android没有像adb这种可以查看内存、cpu的命令.在日常做性能测试,需要借助xcode中instruments查看内存、cpu等数据.

但是借助instruments比较麻烦、又不能提供命令行.在持续集成中,很难时时的监控app的性能指标.并且现在app发版一般是2周左右,留给做专项测试的时间更少了,那么做核心场景性能测试,肯定是来不及的.

所以需要借助一些自动化工具来减轻手工采集性能指标的工作量.

性能采集项
app中基本性能采集项,内存、cpu、fps、电量等,因为自动化采集中手机设备是插着电脑充电的,所以不能采集电量数据.

已有工具
instruments是官方提供的,不能做到自动化采集

腾讯gt,需要在app中集成sdk,有一定的接入成本

第三sdk,类似腾讯gt需要在app集成,可能会有数据泄漏风险

脚本开发
上述的已有工具都不满足,在持续集成中做到自动化采集性能数据,期望的性能测试工具有一下几点:

方便接入

可生成性能报告

可持续化

数据收集精准

所以基于这几点,需要自己开发一套性能采集脚本.

使用官方提供的api做性能采集
获取内存、cpu等

import

/**

  • 获取内存
    */
  • (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 %srn", [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.png
如何在持续集成中使用
monkey和UI自动化中使用,最终会发送一份性能报告.

Demo代码
已经把性能代码脱了主项目,可在Demo代码中编译,github地址:https://github.com/xinxi1990/iOSPerformanceTest

最后
虽然iOS生态封闭,但是对于开发者和测试者还是有一些空间可以利用的.

iOS测试一直都是一个难点,难懂的oc语法和iOS整体框架.如果你开始慢慢接触iOS,会发现iOS测试也并不是那么难,需要一点耐心和一点专心而已.

(文章来源于霍格沃兹测试学院)

更多优秀内容及资料可点击获取

相关实践学习
通过性能测试PTS对云服务器ECS进行规格选择与性能压测
本文为您介绍如何利用性能测试PTS对云服务器ECS进行规格选择与性能压测。
相关文章
|
2天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
16 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
5天前
|
测试技术 开发者 UED
探索软件测试的深度:从单元测试到自动化测试
【10月更文挑战第30天】在软件开发的世界中,测试是确保产品质量和用户满意度的关键步骤。本文将深入探讨软件测试的不同层次,从基本的单元测试到复杂的自动化测试,揭示它们如何共同构建一个坚实的质量保证体系。我们将通过实际代码示例,展示如何在开发过程中实施有效的测试策略,以确保软件的稳定性和可靠性。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
2天前
|
监控 算法 iOS开发
深入探索iOS函数调用栈:符号化与性能调优实战
在iOS开发中,理解函数调用栈对于性能调优和问题排查至关重要。函数调用栈记录了程序执行过程中的函数调用顺序,通过分析调用栈,我们可以识别性能瓶颈和潜在的代码问题。本文将分享iOS函数调用栈的基本概念、符号化过程以及如何利用调用栈进行性能调优。
17 2
|
3天前
|
jenkins 测试技术 持续交付
软件测试中的自动化测试策略
在当今快速发展的软件行业中,自动化测试已成为确保软件质量和效率的关键工具。本文将探讨自动化测试的重要性、实施策略以及面临的挑战,旨在为软件开发团队提供实用的指导和建议。
|
5天前
|
测试技术 Android开发 UED
探索软件测试中的自动化框架选择
【10月更文挑战第29天】 在软件开发的复杂过程中,测试环节扮演着至关重要的角色。本文将深入探讨自动化测试框架的选择,分析不同框架的特点和适用场景,旨在为软件开发团队提供决策支持。通过对比主流自动化测试工具的优势与局限,我们将揭示如何根据项目需求和团队技能来选择最合适的自动化测试解决方案。此外,文章还将讨论自动化测试实施过程中的关键考虑因素,包括成本效益分析、维护难度和扩展性等,确保读者能够全面理解自动化测试框架选择的重要性。
20 1
|
8天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
20 2
|
2天前
|
测试技术 持续交付
软件测试中的自动化测试策略与最佳实践
【10月更文挑战第31天】 在当今快速迭代的软件开发环境中,自动化测试成为确保软件质量和加速产品上市的关键。本文探讨了自动化测试的重要性、实施策略以及一些最佳实践。通过分析不同类型的自动化测试工具和框架,本文旨在为软件开发团队提供一套实用的指导方案,以提高测试效率和质量。
|
3天前
|
Web App开发 测试技术 数据安全/隐私保护
自动化测试的魔法:使用Python进行Web应用测试
【10月更文挑战第32天】本文将带你走进自动化测试的世界,通过Python和Selenium库的力量,展示如何轻松对Web应用进行自动化测试。我们将一起探索编写简单而强大的测试脚本的秘诀,并理解如何利用这些脚本来确保我们的软件质量。无论你是测试新手还是希望提升自动化测试技能的开发者,这篇文章都将为你打开一扇门,让你看到自动化测试不仅可行,而且充满乐趣。
|
6天前
|
测试技术 持续交付
探索软件测试中的自动化框架:优势与挑战
【10月更文挑战第28天】 随着软件开发的快速进步,自动化测试已成为确保软件质量的关键步骤。本文将探讨自动化测试框架的优势和面临的挑战,以及如何有效地克服这些挑战。
16 0
|
6天前
|
Web App开发 设计模式 JavaScript
自动化测试之美:如何利用Selenium实现Web应用的高效测试
【10月更文挑战第29天】在软件开发的世界中,测试是确保产品质量的关键步骤。本文将带你了解如何使用Selenium这一强大的自动化测试工具,提高Web应用测试的效率和准确性。通过实际案例,我们将探索Selenium的核心功能及其在现代软件开发中的应用,旨在帮助读者掌握自动化测试的精髓,从而提升软件测试工作的整体效能。