CTS测试框架 -- V2版本

目录

  • 概述
  • 组织case 
    • 入口CompatibilityConsole
    • ModuleRepo
    • 组件CompatibilityTest
    • 执行测试
  • 总结

1 概述

在Android 6.0以及之前的版本上,CTS测试使用的都是前面介绍的V1框架,上篇文章已经介绍了V1框架的组织case的方式以及不足,主要是当测试case不断的增加之后带来的配置文件的不断变大,各模块之间的接耦合成都还是不够,因此就有了V2版本。 
V2版本的组件名称都已经不以CTS作为名称了,变成了以Compatibility作为关键词,从名称上也可以大致看出来这个测试框架应该是变得更加通用了,CTS只是其中一个。其实,事实也确实是如此,因为随着Android O的发布,Google还推出了一个新的测试计划 – VTS,其实这个VTS框架的变化很小,基本就是在V2版本上稍加配置完成的,在本篇文章的结尾也会介绍下VTS测试框架的组成。

2 组织case

框架再变,主要功能还是不变,就是为了把那么多的case更好的组织起来,顺利跑完所有的测试case,因此重点还是在测试case的组织上。

2.1 入口CompatibilityConsole

代码位置:/cts/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/

CompatibilityConsole同样也是基础框架中Console的子类,启动方式没有变化,其中主要还是添加了自定义Command,setCustomCommands这个方法中添加了V2框架支持自己支持的命令,可以看到在自定义的命令中多了一些关于module的命令,这个module非常重要,是整个V2框架的重心,在V2框架的测试case执行中,淡化了plan的概念,强调了这个module的概念,一个module就代表一组测试case,简单的理解,对于以apk为单位测试的case,一个apk就是一个module。 
举个例子:其中的listModules

private void listModules() {File[] files = null;try {// 获取测试目录// 这个ModuleRepo.ConfigFilter主要作用就是获取所有config结尾的文件files = getBuildHelper().getTestsDir().listFiles(new ModuleRepo.ConfigFilter());} catch (FileNotFoundException e) {printLine(e.getMessage());e.printStackTrace();}if (files != null && files.length > 0) {List<String> modules = new ArrayList<>();for (File moduleFile : files) {// 遍历目录下的所有文件// 把config结尾的文件的文件名列出来modules.add(FileUtil.getBaseName(moduleFile.getName()));}Collections.sort(modules);for (String module : modules) {printLine(module);}} else {printLine("No modules found");}
}

这个地方ModuleRepo是一个重点,测试case的组织全靠这个repo,下一小节重点介绍。

如果运行过CTS测试case的话,会知道在测试运行的时候控制台上命令提示符前面会有cts-tf的提示,之前的版本是写死在代码中的,而这里就不一样了:

@Override
protected String getConsolePrompt() {return String.format("%s-tf > ", SuiteInfo.NAME.toLowerCase());
}

其中的SuiteInfo并不是某一个固定的java文件,而是动态编译生成的:

代码位置:/cts/build/compatibility_test_suite.mk

这个mk文件定义了SuiteInfo文件的生成:

# Generate the SuiteInfo.java
suite_info_java := $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE),true,COMMON)/com/android/compatibility/SuiteInfo.java
$(suite_info_java): PRIVATE_SUITE_BUILD_NUMBER := $(LOCAL_SUITE_BUILD_NUMBER)
$(suite_info_java): PRIVATE_SUITE_TARGET_ARCH := $(LOCAL_SUITE_TARGET_ARCH)
$(suite_info_java): PRIVATE_SUITE_NAME := $(LOCAL_SUITE_NAME)
$(suite_info_java): PRIVATE_SUITE_FULLNAME := $(LOCAL_SUITE_FULLNAME)
$(suite_info_java): PRIVATE_SUITE_VERSION := $(LOCAL_SUITE_VERSION)
$(suite_info_java): cts/build/compatibility_test_suite.mk $(LOCAL_MODULE_MAKEFILE)@echo Generating: $@$(hide) mkdir -p $(dir $@)$(hide) echo "/* This file is auto generated by Android.mk.  Do not modify. */" > $@$(hide) echo "package com.android.compatibility;" >> $@$(hide) echo "public class SuiteInfo {" >> $@$(hide) echo "    public static final String BUILD_NUMBER = \"$(PRIVATE_SUITE_BUILD_NUMBER)\";" >> $@$(hide) echo "    public static final String TARGET_ARCH = \"$(PRIVATE_SUITE_TARGET_ARCH)\";" >> $@$(hide) echo "    public static final String NAME = \"$(PRIVATE_SUITE_NAME)\";" >> $@$(hide) echo "    public static final String FULLNAME = \"$(PRIVATE_SUITE_FULLNAME)\";" >> $@$(hide) echo "    public static final String VERSION = \"$(PRIVATE_SUITE_VERSION)\";" >> $@$(hide) echo "}" >> $@
# Include the SuiteInfo.java
LOCAL_GENERATED_SOURCES := $(suite_info_java)

这个文件重点就是生成了一些常量,而这些常量也正是在mk文件中定义的:比如我们上面说的SuiteInfo.NAME,生成过程:

public static final String NAME = \"$(PRIVATE_SUITE_NAME)\";" >> $@
$(suite_info_java): PRIVATE_SUITE_NAME := $(LOCAL_SUITE_NAME)

LOCAL_SUITE_NAME的定义:

/cts/tools/cts-tradefed/Android.mk

其中有一行LOCAL_SUITE_NAME := CTS 
上面的文件中还有一些其他常量的定义。 
虽然这个只是一个名称的定义,但是意义在于整个V2版本的框架的灵活程度变的更高了,尽可能的把更多的内容放在mk文件中定义,避免hard code。 
入口这个文件还是比较简单,就是作为整个框架的启动入口,自定义命令的添加。

2.2 ModuleRepo

在V2版本的框架中淡化的plan的概念,取而代之是module,开始之前我们先看下这个module的config文件究竟长什么样,跟V1版本的有什么区别,依旧是CtsJobSchedulerTestCases:

<configuration description="Config for CTS Job Scheduler test cases"><option name="config-descriptor:metadata" key="component" value="framework" /><target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"><option name="cleanup-apks" value="true" /><option name="test-file-name" value="CtsJobSchedulerTestCases.apk" /><option name="test-file-name" value="CtsJobSchedulerJobPerm.apk" /></target_preparer><test class="com.android.tradefed.testtype.AndroidJUnitTest" ><option name="package" value="android.jobscheduler.cts" /><option name="runtime-hint" value="2m" /></test>
</configuration>

可以看到,没有了之前每条测试都配置一个xml中的一个标签,取而代之的是这个module相关的一些配置。 
在V1版本中测试case的组织是通过TestPackageRepo,而这里是通过这个ModuleRepo,在初始化的时候扫描测试目录下所有的config文件

private void addModuleDef(String name, IAbi abi, IRemoteTest test,String[] configPaths) throws ConfigurationException {// Invokes parser to process the test module config fileIConfiguration config = mConfigFactory.createConfigurationFromArgs(configPaths);addModuleDef(new ModuleDef(name, abi, test, config.getTargetPreparers(),config.getConfigurationDescription()));
}

每个config文件代表了一个module,把所有的config文件解析之后放入list。

2.3 组件CompatibilityTest

老套路,还是先看下这个V2框架的组件配置文件:

代码位置 
platform/cts/common/host-side/tradefed/res/config/ 
platform/cts/tools/cts-tradefed/res/config

这次的配置文件比较多,这个地方就不贴代码了,但是核心没有变,组件的配置在/cts/common/host-side/tradefed/res/config/common-compatibility-config.xml中,有一个test组件的配置<test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTest" />,可见V2框架的test组件就是这个CompatibilityTest,直奔其run方法:

public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {try {List<ISystemStatusChecker> checkers = new ArrayList<>();// 系统状态检查if (mSkipAllSystemStatusCheck) {CLog.d("Skipping system status checkers");} else {checkSystemStatusBlackAndWhiteList();for (ISystemStatusChecker checker : mListCheckers) {if(shouldIncludeSystemStatusChecker(checker)) {checkers.add(checker);}}}LinkedList<IModuleDef> modules;synchronized (mModuleRepo) {if (!mModuleRepo.isInitialized()) {// 这步很重要,初始化了filter// 一个代表要删除掉的module,一个代表要添加的额外modulesetupFilters();// ModuleRepo的初始化,已经添加了所有的测试casemModuleRepo.initialize(mTotalShards, mShardIndex, mBuildHelper.getTestsDir(),getAbis(), mDeviceTokens, mTestArgs, mModuleArgs, mIncludeFilters,mExcludeFilters,mModuleMetadataIncludeFilter, mModuleMetadataExcludeFilter,mBuildHelper.getBuildInfo());// Add the entire list of modules to the CompatibilityBuildHelper for reportingmBuildHelper.setModuleIds(mModuleRepo.getModuleIds());int count = UniqueModuleCountUtil.countUniqueModules(mModuleRepo.getTokenModules()) +UniqueModuleCountUtil.countUniqueModules(mModuleRepo.getNonTokenModules());CLog.logAndDisplay(LogLevel.INFO, "========================================");CLog.logAndDisplay(LogLevel.INFO, "Starting a run with %s unique modules.",count);CLog.logAndDisplay(LogLevel.INFO, "========================================");} else {CLog.d("ModuleRepo already initialized.");}// 获取本次测试要跑的module的集合modules = mModuleRepo.getModules(getDevice().getSerialNumber(), mShardIndex);}// clearFilter,就是前面提到的一个代表要删除掉的module,一个代表要添加的额外modulemExcludeFilters.clear();mIncludeFilters.clear();if (mRetrySessionId != null) {loadRetryCommandLineArgs(mRetrySessionId);}listener = new FailureListener(listener, getDevice(), mBugReportOnFailure,mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure, mMaxLogcatBytes);int moduleCount = modules.size();if (moduleCount == 0) {if (sPreparedLatch != null) {sPreparedLatch.countDown();}return;} else {int uniqueModuleCount = UniqueModuleCountUtil.countUniqueModules(modules);}if (mRebootBeforeTest) {mDevice.reboot();}if (mSkipConnectivityCheck) {String clazz = NetworkConnectivityChecker.class.getCanonicalName();mSystemStatusCheckBlacklist.add(clazz);}boolean isPrepared = true;for (int i = 0; i < moduleCount; i++) {IModuleDef module = modules.get(i);module.setBuild(mBuildHelper.getBuildInfo());module.setDevice(mDevice);module.setPreparerWhitelist(mPreparerWhitelist);// 开始对每个module设置组件以及deviceif (mCollectTestsOnly != null) {module.setCollectTestsOnly(mCollectTestsOnly);}isPrepared &= (module.prepare(mSkipPreconditions, mPreconditionArgs));}if (!isPrepared) {throw new RuntimeException(String.format("Failed preconditions on %s",mDevice.getSerialNumber()));}if (mIsLocalSharding) {try {sPreparedLatch.countDown();int attempt = 1;while(!sPreparedLatch.await(MINUTES_PER_PREP_ATTEMPT, TimeUnit.MINUTES)) {if (attempt > NUM_PREP_ATTEMPTS ||InvocationFailureHandler.hasFailed(mBuildHelper)) {CLog.logAndDisplay(LogLevel.ERROR,"Incorrect preparation detected, exiting test run from %s",mDevice.getSerialNumber());return;}CLog.logAndDisplay(LogLevel.WARN, "waiting on preconditions");attempt++;}} catch (InterruptedException e) {throw new RuntimeException(e);}}mModuleRepo.tearDown();mModuleRepo = null;// 开始执行测试while (!modules.isEmpty()) {IModuleDef module = modules.poll();long start = System.currentTimeMillis();if (mRebootPerModule) {if ("user".equals(mDevice.getProperty("ro.build.type"))) {CLog.e("reboot-per-module should only be used during development, "+ "this is a\" user\" build device");} else {mDevice.reboot();}}// 运行测试检查if (checkers != null && !checkers.isEmpty()) {runPreModuleCheck(module.getName(), checkers, mDevice, listener);}IInvocationContext moduleContext = new InvocationContext();moduleContext.setConfigurationDescriptor(module.getConfigurationDescriptor());moduleContext.addInvocationAttribute(IModuleDef.MODULE_NAME, module.getName());moduleContext.addInvocationAttribute(IModuleDef.MODULE_ABI,module.getAbi().getName());mInvocationContext.setModuleInvocationContext(moduleContext);try {// 执行modulemodule.run(listener);} catch (DeviceUnresponsiveException due) {// being able to catch a DeviceUnresponsiveException here implies that recovery// was successful, and test execution should proceed to next moduleByteArrayOutputStream stack = new ByteArrayOutputStream();due.printStackTrace(new PrintWriter(stack, true));StreamUtil.close(stack);} finally {mInvocationContext.setModuleInvocationContext(null);}long duration = System.currentTimeMillis() - start;long expected = module.getRuntimeHint();long delta = Math.abs(duration - expected);// Show warning if delta is more than 10% of expectedif (expected > 0 && ((float)delta / (float)expected) > 0.1f) {CLog.logAndDisplay(LogLevel.WARN,"Inaccurate runtime hint for %s, expected %s was %s",module.getId(),TimeUtil.formatElapsedTime(expected),TimeUtil.formatElapsedTime(duration));}if (checkers != null && !checkers.isEmpty()) {runPostModuleCheck(module.getName(), checkers, mDevice, listener);}module = null;}} catch (FileNotFoundException fnfe) {throw new RuntimeException("Failed to initialize modules", fnfe);}
}

可以发现这个run方法中并没有想V1版本的框架一样,根据plan文件去组织case,但是运行命令的时候plan参数还是可用的,这是怎么回事呢? 
其实是V1和V2的很重要的一点区别就是在这里: 
v1版本的plan文件相当于是一个集合,需要执行哪些测试case呢,就把需要执行的测试case添加到plan中,最后把这个集合中的测试case拿出来执行即可。 
v2版本则不然,它默认就把所有的测试case给全部拿到并执行,除非配置了不需要执行哪些case,否则的话默认执行全部的case。 
这就是执行cts这个plan的时候还是会跑全部的case的原因了,因为其实不管你在执行的时候plan是谁,都是跑全部的case,如果不想跑全部的case的话,就需要去特殊定制配置文件:比如cts-java.xml 
其中配置了<option name="compatibility:include-filter" value="CtsLibcoreTestCases" />,也就是说通过include-filter以及exclude-filter两个filter去特殊定制指定的plan。

2.4 执行测试

前面已经看到,在CompatibilityTest中执行测试的执行了module.run,这个方法就是去执行测试了,但是好像跟V1版本不一样啊,V1版本因为所有的case都已经在xml文件中注明了,解析完毕就已经知道要执行的测试case了,所以其实直接就进入了执行命令的步骤了,然而这里还是不一样,在ModuleDef的run方法:

public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {CLog.d("Running module %s", toString());// Run DynamicConfigPusher setup once more, in case cleaner has previously// removed dynamic config file from the target (see b/32877809)for (ITargetPreparer preparer : mDynamicConfigPreparers) {runPreparerSetup(preparer);}// Setupfor (ITargetPreparer preparer : mPreparers) {runPreparerSetup(preparer);}CLog.d("Test: %s", mTest.getClass().getSimpleName());if (mTest instanceof IAbiReceiver) {((IAbiReceiver) mTest).setAbi(mAbi);}if (mTest instanceof IBuildReceiver) {((IBuildReceiver) mTest).setBuild(mBuild);}if (mTest instanceof IDeviceTest) {((IDeviceTest) mTest).setDevice(mDevice);}IModuleListener moduleListener = new ModuleListener(this, listener);// Guarantee events testRunStarted and testRunEnded in case underlying test runner does notModuleFinisher moduleFinisher = new ModuleFinisher(moduleListener);mTest.run(moduleFinisher);moduleFinisher.finish();// Tear downfor (ITargetCleaner cleaner : mCleaners) {CLog.d("Cleaner: %s", cleaner.getClass().getSimpleName());cleaner.tearDown(mDevice, mBuild, null);}
}

看这个地方好像有些似曾相识,再看前面的config的配置文件,联想到框架,其实从本质上v1和v2的区别就在这里,v1是通过框架把所有的每一条测试case都拿到,逐个去执行,但是V2则是把每个测试module都作为一个configuration,框架做的就是去拿到测试的所有module,但是每个module的执行还是走了框架,因为每个module现在都被认为是一个configuration了,只需要去逐个执行测试module即可。

4 总结

到这里V2框架也说的差不多了,V2框架介绍没有贴太多的代码,一方面是本身V2框架相V1的组织逻辑就简单一些,另外一方面是因为两者的概念不同,v2把每个module作为一个configuration去处理,各个module之间都是独立的,并不需要像V1框架那样再需要一个xml文件去配置,需要plan文件去做一个集合。 
另外VTS其实入口也是CompatibilityConsole,运行方式跟这个一样,包括前面SuiteInfo文件的生成,也是跟CTS如出一辙。

下篇文章介绍下添加case与自定义,以及系列总结。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011733869/article/details/78879563

CTS(20)---CTS测试框架 -- V2版本相关推荐

  1. CTS测试框架 -- V1版本

    目录 概述 组织case CTS框架配置文件 测试case配置文件 启动框架CtsConsole test组件CtsTest 测试类型 执行命令 总结 1 概述 CTS测试框架是有两个版本的,Andr ...

  2. 砥砺前行 | Kratos 框架 v2 版本架构演进之路

    Kratos 是一套轻量级 Go 微服务框架,包含大量微服务相关功能及工具.名字来源于游戏<战神>,该游戏以希腊神话为背景,讲述了奎托斯(Kratos)由凡人成为战神并展开弑神屠杀的冒险历 ...

  3. 补充轻量级持久层V2版本的测试页面模板与实体类模板

    前天做完忘记备份,所以昨天没有发上来,文件下载请到<轻量级持久层框架V2版本代码与模板> 需要说明下,测试页面的代码模板不是直接生成就可以用的,需要在我注释掉代码的部分添加自己的测试数据, ...

  4. 使用python构造大量测试数据_python实例编写(6)--引入unittest测试框架,构造测试集批量测试(以微信统一管理平台为例)...

    ---恢复内容开始--- 一.python单元测试实例介绍 unittest框架又叫PyUnit框架,是python的单元测试框架. 先介绍一个普通的单元测试(不用unittest框架)的实例: 首先 ...

  5. CTS、CTS Verify、GTS测试以及GMS认证

    目录 一.一些概念 二.GMS认证 2.1 认证背景 2.2 GMS认证目的 2.3 GMS认证要求 2.4 GMS认证流程 2.5 MADA协议申请流程 三.CTS测试 3.1 CTS测试介绍 3. ...

  6. CTS、CTS Verify、GTS测试以及GMS认证介绍

    一.一些概念 ① CTS测试 以自动化测试为基础,测试Android系统的兼容性: ② CTS Verify 一个手工测试的apk,完成自动化测试无法完成的操作(锁屏.蓝牙.摄像头等): ③ GTS ...

  7. CTS(3)---CTS 测试的一点心得

    CTS 测试的一点心得 CTS介绍-----------------------------------------------3 什么是CTS---------------------------- ...

  8. CTS(13)---CTS 测试之Media相关测试failed 小结(一)

    Android o CTS 测试之Media相关测试failed 小结(一) CTS CTS 即兼容性测试套件,CTS 在桌面设备上运行,并直接在连接的设备或模拟器上执行测试用例.CTS 是一套单元测 ...

  9. 走进Java接口测试大门之测试框架TestNG

    一.简介 TestNG 是一个受 JUnit 和 NUnit 启发的测试框架,旨在简化广泛的测试需求,从单元测试到接口测试. 但引入了一些新功能,使其更强大,更易于使用,例如: 注释. 在线程池中运行 ...

最新文章

  1. 【经典书】图论,322页pdf
  2. 破解IDEA2018的正确姿势
  3. phpmyadmin忘记mysql密码_忘记phpmyadmin登录密码怎么办
  4. 你经常使用计算机吗这样的问题其主要缺点是,2015年10月自考02326操作系统模拟试题及答案3...
  5. 在plist文件中增删改查
  6. 翼支付和银行网络连通准备
  7. 为什么11·11物流一年比一年快?奥秘就在这里!
  8. 62. WWW 服务器
  9. Metadata Lock原理4
  10. web前端基础入门教程(非常详细)HTML+CSS+JavaScript
  11. jquery进度条插件
  12. 谷歌浏览器如何使用访客模式 谷歌浏览器使用访客模式的方法
  13. 经典面试题 Ipv4 和 Ipv6 是什么
  14. 齐岳|马钱苷酸小麦麦清白蛋白纳米粒|雷公藤红素乳清白蛋白纳米粒Celastrol-whey protein
  15. Open Wifi SSID Broadcast vulnerability
  16. AirDrop显示名字的修改问题
  17. 姿态估计1-04:FSA-Net(头部姿态估算)-白话给你讲论文-翻译无死角(2)
  18. 美团点评Cat业务实践
  19. 小猿圈python_小猿圈Python配置gRPC环境
  20. SpringBoot 腾讯企业邮箱

热门文章

  1. Linux内核分析 - 网络[三]:从netif_receive_skb()说起
  2. mysql 两行的差异_MySQL两行之间的日期差异
  3. mysql取消外键限制_mysql怎么取消外键限制(约束)?
  4. calendar获取月份少一个月_VBA 技巧:计算一个月有多少天?
  5. 《RabbitMQ 实战指南》第一章 RabbitMQ 简介
  6. 【LeetCode】剑指 Offer 15. 二进制中1的个数
  7. 【Java数据结构与算法】第二十章 Dijkstra算法和Floyd算法
  8. char赋值字符串常量和数值的区别
  9. linux vim文本编辑器
  10. CentOS7 iptables安装及操作