uml 图

以"作孽"的UML知识画成的关系图:

解释

上图涉及 5 个类:ddmlib提供的TestIdentifier类,cts自定义的IRemoteTest接口、ITestPackageDef接口、TestFilter类和TestPackage类。具体意义如下:

TestIdentifier

ddmlib为我们提供的属于instrumentation测试的case实体类,里面有 2 个属性,case 的类名和方法名。这样我们就可以找到一个 case。

IRemoteTest

一种测试类型的接口定义,该测试类型可以直接将结果发送到监听器,让其处理。所以测试 runner 部分只管撒了欢的跑,抓结果的模块是单独存在的。

ITestPackageDef

case 相关信息的容器,信息从哪里来?举个例子,写uiautomator case的人都了解,我们写的 case 都要打成 jar 包,随着 jar 一起生成的还有一个与 jar 包名一样的 xml 文件,我们的信息就是从该 xml 文件里来,里面定义了 jar 包的标识符,测试包名等信息。

TestFilter

case 的过滤器类,定义一些过滤的条件。里面有 2 个集合保存了要被过滤掉的 case 的类名或TestIdentifier对象,以及 1 个特殊的类名和方法名,这个属性默认是为null,一般情况下需要你去设值,如果不设置,那么这个条件就不作为过滤的条件,来具体看看删选的处理代码:

public Collection<TestIdentifier> filter(Collection<TestIdentifier > tests) {
        List<TestIdentifier> filteredTests = new ArrayList<TestIdentifier>(tests.size());
        for (TestIdentifier test : tests) {
            if (mIncludedClass != null && !test.getClassName().equals(mIncludedClass)) {
                // skip
                continue;
            }
            if (mIncludedMethod != null && !test.getTestName().equals(mIncludedMethod)) {
                // skip
                continue;
            }
            if (mExcludedClasses.contains(test.getClassName())) {
                // skip
                continue;
            }
            if (mExcludedTests.contains(test)) {
                // skip
                continue;
            }
            filteredTests.add(test);
        }
        Collections.sort(filteredTests, new TestIdComparator());
        return filteredTests;
    }

代码块很简单,随着条件分支一步一步过滤,最后剩下来的 case 添加到新集合中返回。

TestPackage

包含上面 3 个实体对象 (除了TestFilter),一个 TestPackage 代表了一个 case 包 (jar 或者 apk 等) 相关的所有信息,例如一个uiautomator写出的 jar 包,那么一个 jar 包就需要定义一个TestPackage对象:该 jar 包包含的 case 集合,该 jar 包执行的测试类型,以及 jar 一些相关属性信息。有了这些就足够了,case 就可以执行 run 的动作了。cts 执行的时候只需要得到 TestPackage 对象集合 (代表一个个的 case 包对象),就可以遍历得到所有要执行的 case。

具体执行过程

cts 中是以 plan 来定义要跑的 case 包集合,plan 则是一个 xml 文件,里面定义了一个或多个 case 包的标识信息。这样去 case 的目录下就可以找到 case 包以及 case 包的 xml 配置文件。当我们传入一个 plan 进入 cts 后,发生了什么?(一下方法都是 CtsTest 中的方法)

buildTestsToRun 方法

当测试执行的时候,cts 会先调用该方法获得所有 TestPackage 对象,上面说过,获得这个就足够了。具体该实现:

private List<TestPackage> buildTestsToRun() {
        List<TestPackage> testPkgList = new LinkedList<TestPackage>();
        try {
            // 获得testcases目录下所有的xml文件解析出来的case包对象
            ITestPackageRepo testRepo = createTestCaseRepo();
            // 得到本次plan所需跑的case
            Collection<ITestPackageDef> testPkgDefs = getTestPackagesToRun(testRepo);

            for (ITestPackageDef testPkgDef : testPkgDefs) {
                addTestPackage(testPkgList, testPkgDef);
            }
            if (testPkgList.isEmpty()) {
                Log.logAndDisplay(LogLevel.WARN, LOG_TAG, "No tests to run");
            }
        } catch (FileNotFoundException e) {
            throw new IllegalArgumentException("failed to find CTS plan file", e);
        } catch (ParseException e) {
            throw new IllegalArgumentException("failed to parse CTS plan file", e);
        } catch (ConfigurationException e) {
            throw new IllegalArgumentException("failed to process arguments", e);
        }
        return testPkgList;
    }

首先会去将固定路径下的 (cts 根目录下的 repository\testcases) 下所有 xml 文件解析出来,这些 xml 文件都是和 case 包一一对应的,你不能去解析 jar 包或者 apk 包吧,所以需要一个 case 包配置文件的存在,这样我们读取 xml 的信息就可以得到相关的信息。然后我们要筛选出本次 plan 需要跑的 case 包。

getTestPackagesToRun 方法

该方法里有 4 条分支,每条分支代表不同的执行任务的标识。
1以 plan 名定义的任务
2.以 case 包的 uri 定义的集合所定义的任务
3.以 class 定义的 (一个类中的所有 case) 任务
4.以 sessionID(cts 为之前跑过的任务都定义了一个 session) 所定义的任务,这个是重跑之前的任务。

我们来只看第一种,以 plan 方式启动的任务。

private Collection<ITestPackageDef> getTestPackagesToRun(ITestPackageRepo testRepo) throws ParseException, FileNotFoundException, ConfigurationException {
        // use LinkedHashSet to have predictable iteration order
        Set<ITestPackageDef> testPkgDefs = new LinkedHashSet<ITestPackageDef>();
        if (mPlanName != null) {
            Log.i(LOG_TAG, String.format("Executing CTS test plan %s", mPlanName));
            File ctsPlanFile = mCtsBuildHelper.getTestPlanFile(mPlanName);
            ITestPlan plan = createPlan(mPlanName);
            plan.parse(createXmlStream(ctsPlanFile));
            for (String uri : plan.getTestUris()) {
                if (!mExcludedPackageNames.contains(uri)) {
                    ITestPackageDef testPackage = testRepo.getTestPackage(uri);
                    testPackage.setExcludedTestFilter(plan.getExcludedTestFilter(uri));
                    testPkgDefs.add(testPackage);
                }
            }
        } else if (mPackageNames.size() > 0) {
            ......
        } else if (mClassName != null) {
            ......
        } else if (mContinueSessionId != null) {
            ......
        } else {
            // should never get here - was checkFields() not called?
            throw new IllegalStateException("nothing to run?");
        }
        return testPkgDefs;
    }

该分支中,首先根据 plan 名找到所有 xml 文件,然后解析 xml 文件,得到 case。这样我们就得到了 ITestPackageDef 对象,遍历得到所有这样的对象,然后将该对象集合返回。回到上面的buildTestsToRun()中,然后根据返回的集合元素创建TestPackage对象集合,这样我们的处理过程就完成了。


↙↙↙阅读原文可查看相关链接,并与作者交流