天道辅文 發表於 2022-3-9 13:01:00

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

<p>今天小编跟大家分享一篇来自学院内部学员的技术分享,本文主要介绍了作者在进行 iOS 自动化性能采集的一些经验,希望对大家在进行 iOS<br>
自动化测试时有一些启发。</p>
<p>作者: <strong>xinxi</strong> ,某互联网公司测试开发工程师,霍格沃兹测试学院北京三期学员,喜欢养狗、旅游和篮球,更爱测试技术分享。</p>
<p>不要为小事遮住视线,我们还有更大的世界</p>
<h2 id="前言">前言</h2>
<p>对于iOS总体生态是比较封闭的,相比Android没有像adb这种可以查看内存、cpu的命令.在日常做性能测试,需要借助xcode中instruments查看内存、cpu等数据.</p>
<p>但是借助instruments比较麻烦、又不能提供命令行.在持续集成中,很难时时的监控app的性能指标.并且现在app发版一般是2周左右,留给做专项测试的时间更少了,那么做核心场景性能测试,肯定是来不及的.</p>
<p>所以需要借助一些自动化工具来减轻手工采集性能指标的工作量.</p>
<h2 id="性能采集项">性能采集项</h2>
<p>app中基本性能采集项,内存、cpu、fps、电量等,因为自动化采集中手机设备是插着电脑充电的,所以不能采集电量数据.</p>
<h2 id="已有工具">已有工具</h2>
<ul>
<li>
<p>instruments是官方提供的,不能做到自动化采集</p>
</li>
<li>
<p>腾讯gt,需要在app中集成sdk,有一定的接入成本</p>
</li>
<li>
<p>第三sdk,类似腾讯gt需要在app集成,可能会有数据泄漏风险</p>
</li>
</ul>
<h2 id="脚本开发">脚本开发</h2>
<p>上述的已有工具都不满足,在持续集成中做到自动化采集性能数据,期望的性能测试工具有一下几点:</p>
<ul>
<li>
<p>方便接入</p>
</li>
<li>
<p>可生成性能报告</p>
</li>
<li>
<p>可持续化</p>
</li>
<li>
<p>数据收集精准</p>
</li>
</ul>
<p>所以基于这几点,需要自己开发一套性能采集脚本.</p>
<h2 id="使用官方提供的api做性能采集">使用官方提供的api做性能采集</h2>
<h3 id="获取内存cpu等">获取内存、cpu等</h3>
<pre><code>#import &lt;mach/mach.h&gt;

/**
*获取内存
*/
- (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) &amp;vmInfo, &amp;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 = ;

    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, &amp;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(), &amp;thread_list, &amp;thread_count);
    if (kr != KERN_SUCCESS) {
      return [ NSString stringWithFormat: @"%f" ,-1];
    }
    if (thread_count &gt; 0)
      stat_thread += thread_count;

    long tot_sec = 0;
    long tot_usec = 0;
    float tot_cpu = 0;
    int j;

    for (j = 0; j &lt; thread_count; j++)
    {
      thread_info_count = THREAD_INFO_MAX;
      kr = thread_info(thread_list, THREAD_BASIC_INFO,
                         (thread_info_t)thinfo, &amp;thread_info_count);
      if (kr != KERN_SUCCESS) {
            tot_cpu = -1;
            //return -1;
      }

      basic_info_th = (thread_basic_info_t)thinfo;

      if (!(basic_info_th-&gt;flags &amp; TH_FLAGS_IDLE)) {
            tot_sec = tot_sec + basic_info_th-&gt;user_time.seconds + basic_info_th-&gt;system_time.seconds;
            tot_usec = tot_usec + basic_info_th-&gt;user_time.microseconds + basic_info_th-&gt;system_time.microseconds;
            tot_cpu = tot_cpu + basic_info_th-&gt;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 (@"performancecpu:%@",tostring);

    return tostring;
}
</code></pre>
<h3 id="获取页面vc">获取页面vc</h3>
<p>上边收集了内存和cpu,还需要在收集数据的同时和页面对应上.这样就清楚了是当前页面的内存和cpu情况.</p>
<pre><code>/**
*获取当前vc
*/
- (UIViewController *) get_vc {
    UIWindow *keyWindow = .keyWindow;
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
      if (]) {
            UITabBarController *tab = (UITabBarController *)keyWindow.rootViewController;
            UINavigationController *nav = tab.childViewControllers;
            DDContainerController *content = ;
            weakSelf.vc = ;
      }
    });
    return self.vc;
}
</code></pre>
<h3 id="获取设备信息">获取设备信息</h3>
<pre><code>/*
*获取设备名称
*/
- (NSString *) get_devicesName {
    NSString *devicesName = .name; //设备名称
    NSLog(@"performance   devicesName:%@", devicesName);
    return devicesName;

}

/*
*获取系统版本
*/
- (NSString *) get_systemVersion{
    NSString *systemVersion = .systemVersion; //系统版本
    NSLog(@"performance   version:%@", systemVersion);
    return systemVersion;
}

/*
*获取设备idf
*/
- (NSString *) get_idf {
    NSString *idf = .identifierForVendor.UUIDString;
    NSLog(@"performance   idf:%@", idf);
    return idf;

}
</code></pre>
<h3 id="数据拼接">数据拼接</h3>
<p>最终要把内存、cpu等数据拼接成字典的形式,方便输出查看</p>
<pre><code>输出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"
}
</code></pre>
<h3 id="开启子线程采集">开启子线程采集</h3>
<pre><code>开一个子线程定时采集数据

/*
* 性能采集子线程
*/

- (void) performancethread {
    NSThread *thread = [ initWithBlock:^{
      NSLog(@"performance   ======get performance======");

      ;

      while (true) {
            DDPerformanceModel *model = ;
            model.time=;
            model.appname=;
            model.appversion=;
            model.idf =;
            model.devicesName =;
            model.version = ;
            model.vcClass = NSStringFromClass(.class);
            model.memory = ;
            model.battery = ;
            model.cpu = ;
            model.fps = self.percount;

            NSString *json = ;

//            printf(" getperformance    %s\r\n", );
            NSLog(@"getperformance model%@", json);
            sleep(5);
      }
    }];
    ;

    NSLog(@"performance   ======continue mainblock======");
}
</code></pre>
<h3 id="初始化性能采集">初始化性能采集</h3>
<pre><code>AppDelegate.m文件中didFinishLaunchingWithOptions方法中用户各种初始化操作,可以在第一行初始化性能采集,
这样app启动以后就可以定时采集数据

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [ performancethread];//获取性能数据

    }
</code></pre>
<h3 id="性能采集日志存储">性能采集日志存储</h3>
<p>一般来说日志存储都是写入到本地log日志,然后读取.但是有两个问题</p>
<ul>
<li>
<p>需要读写文件代码,对于不熟悉oc的人来说比较难</p>
</li>
<li>
<p>因为是定时采集,文件IO操作频繁</p>
</li>
</ul>
<p>所以不考虑存储本地log日志的方式,可以在代码中打印出数据,通过截获当前设备运行的日志获取数据.</p>
<pre><code>模拟器可以使用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 &gt;${logpath} &amp;
</code></pre>
<h2 id="代码插入到工程中">代码插入到工程中</h2>
<p>因为在持续集成中,每次打取的代码都是不带性能测试代码,这些代码是单独写到文件中.在编译项目前,用shell把代码插入到工程中,这样打出来的包才能有采集性能数据功能.</p>
<pre><code>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}

}
</code></pre>
<h2 id="性能数据绘制">性能数据绘制</h2>
<p>在手工和自动化使用插入性能测试代码的app,如果截获性能数据后,可以对数据做性能数据绘制.</p>
<p>用Higcharts或者echarts绘制性能走势图</p>
<h2 id="如何在持续集成中使用">如何在持续集成中使用</h2>
<p>monkey和UI自动化中使用,最终会发送一份性能报告.</p>
<h2 id="demo代码">Demo代码</h2>
<p>已经把性能代码脱了主项目,可在Demo代码中编译,github地址:https://github.com/xinxi1990/iOSPerformanceTest</p>
<h2 id="最后">最后</h2>
<p>虽然iOS生态封闭,但是对于开发者和测试者还是有一些空间可以利用的.</p>
<p>iOS测试一直都是一个难点,难懂的oc语法和iOS整体框架.如果你开始慢慢接触iOS,会发现iOS测试也并不是那么难,需要一点耐心和一点专心而已.</p>
<p>中高级测试开发「名企定向培养」计划</p>
<p>霍格沃兹测试学院联合意向学员与 100 家一线互联网名企建立定向培养体系,4 个月系统强化特训,4 轮名企内推机会,成功入职返还学费。</p>
<p>详情请戳:中高级测试开发「名企定向培养」计划</p>
<p>来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力</p>
<p>点击获取更多信息</p><br><br>
来源:https://www.cnblogs.com/hogwarts/p/15984653.html
頁: [1]
查看完整版本: iOS 测试 | iOS 自动化性能采集