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


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

    作者: xinxi ,某互联网公司测试开发工程师,霍格沃兹测试学院北京三期学员,喜欢养狗、旅游和篮球,更爱测试技术分享。

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

    前言

    对于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测试也并不是那么难,需要一点耐心和一点专心而已.

    中高级测试开发「名企定向培养」计划

    霍格沃兹测试学院联合意向学员与 100 家一线互联网名企建立定向培养体系,4 个月系统强化特训,4 轮名企内推机会,成功入职返还学费。

    详情请戳:中高级测试开发「名企定向培养」计划

    来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力

    点击获取更多信息

  • 相关阅读:
    STL容器 erase的使用陷井
    转:VC++线程同步-事件对象
    VC线程同步方法
    C/C++四种退出线程的方法
    rabbitMQ 常用命令
    Spring @Configuration
    Spring RabbitMQ 延迟队列
    rabbitmq web管理界面 用户管理
    Linux下tar.gz 安装
    Linux下RPM软件包的安装及卸载
  • 原文地址:https://www.cnblogs.com/hogwarts/p/15984653.html
Copyright © 2020-2023  润新知