UML

解释

cts 框架中将 case 的执行和 case 结果的收集分成了独立的 2 个部分,我们称 case 的结果收集叫测试结果收集模块。设计的思路来源为 ddmlib 中的 ITestRunListener 接口,该接口是一个抽象的观察者组件,cts 创建有很多具体的组件。但是这些组件不是一个一个去注册成为监听者的,而是通过 run 方法传递给 ddmlib 中 RemoteAndroidTestRunner 类的。所以只要我们继承了该接口,在 run 的时候把该接口的实例对象传过去,我们就能在对象实例的相应方法中接受信息。但是我们想要在多个地方都能接受到测试结果呢?没关系,cts 为我们准备了 ResultForwarder 类。看具体实现细节:

ResultForwarder

public class ResultForwarder implements ITestInvocationListener {

    private List<ITestInvocationListener> mListeners;

    /**
     * Create a {@link ResultForwarder} with deferred listener setting. Intended
     * only for use by subclasses.
     */
    protected ResultForwarder() {
        mListeners = Collections.emptyList();
    }

    /**
     * Create a {@link ResultForwarder}.
     * 
     * @param listeners
     *            the real {@link ITestInvocationListener}s to forward results
     *            to
     */
    public ResultForwarder(List<ITestInvocationListener> listeners) {
        mListeners = listeners;
    }

    /**
     * Alternate variable arg constructor for {@link ResultForwarder}.
     * 
     * @param listeners
     *            the real {@link ITestInvocationListener}s to forward results
     *            to
     */
    public ResultForwarder(ITestInvocationListener... listeners) {
        mListeners = Arrays.asList(listeners);
    }

    /**
     * Set the listeners after construction. Intended only for use by
     * subclasses.
     * 
     * @param listeners
     *            the real {@link ITestInvocationListener}s to forward results
     *            to
     */
    protected void setListeners(List<ITestInvocationListener> listeners) {
        mListeners = listeners;
    }

    /**
     * Set the listeners after construction. Intended only for use by
     * subclasses.
     * 
     * @param listeners
     *            the real {@link ITestInvocationListener}s to forward results
     *            to
     */
    protected void setListeners(ITestInvocationListener... listeners) {
        mListeners = Arrays.asList(listeners);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void invocationStarted(IBuildInfo buildInfo) {
        for (ITestInvocationListener listener : mListeners) {
            listener.invocationStarted(buildInfo);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void invocationFailed(Throwable cause) {
        for (ITestInvocationListener listener : mListeners) {
            listener.invocationFailed(cause);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void invocationEnded(long elapsedTime) {
        InvocationSummaryHelper.reportInvocationEnded(mListeners, elapsedTime);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TestSummary getSummary() {
        // should never be called
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testLog(String dataName, LogDataType dataType,
            InputStreamSource dataStream) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testLog] dataName: %s", dataName));
        for (ITestInvocationListener listener : mListeners) {
            listener.testLog(dataName, dataType, dataStream);
        }
    }

    /**
     * {@inheritDoc}case开始时显式的调用
     */
    @Override
    public void testRunStarted(String runName, int testCount) {
//      CLog.logAndDisplay(LogLevel.INFO, String
//              .format("[testRunStarted] runName: %s testCount:%d", runName,
//                      testCount));
        for (ITestInvocationListener listener : mListeners) {
            listener.testRunStarted(runName, testCount);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testRunFailed(String errorMessage) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testRunFailed] errorMessage: %s", errorMessage));
        for (ITestInvocationListener listener : mListeners) {
            listener.testRunFailed(errorMessage);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testRunStopped(long elapsedTime) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testRunStopped] elapsedTime: %d", elapsedTime));
        for (ITestInvocationListener listener : mListeners) {
            listener.testRunStopped(elapsedTime);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testRunEnded] elapsedTime: %d", elapsedTime));
        for (ITestInvocationListener listener : mListeners) {
            listener.testRunEnded(elapsedTime, runMetrics);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testStarted(TestIdentifier test) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testStarted] %s", test.toString()));
        for (ITestInvocationListener listener : mListeners) {
            listener.testStarted(test);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testFailed(TestFailure status, TestIdentifier test, String trace) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testFailed] %s", test.toString()));
        for (ITestInvocationListener listener : mListeners) {
            listener.testFailed(status, test, trace);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
//      CLog.logAndDisplay(LogLevel.INFO,
//              String.format("[testEnded] %s", test.toString()));
        for (ITestInvocationListener listener : mListeners) {
            listener.testEnded(test, testMetrics);
        }
    }
}

该类中继承于ITestInvocationListener接口,ITestInvocationListener也继承与ITestRunListener接口,然后在ITestRunListener接口接触上添加了一些 cts 自定义的方法。

而我们具体看看ResultForwarder构造方法你会发现,传递的参数是多个ITestInvocationListener对象,传递到ResultForwarder后保存到List<ITestInvocationListener>集合中。这样当我们ResultForwarder传递到ddmlib内部的时候,ResultForwarder相应方法被调用后,集合里的每一个ITestInvocationListener对象都会收到结果。良好的设计啊!

ResultForwarder 子类

当 case 执行失败的时候,cts 为我们准备了 3 个处理类,这三个类都是继承与ResultForwarder,因此它们都是 case 执行结果的监听器,case 失败的时候会自动的进行相关的动作。

/**
     * 保存bugreport日志 A {@link ResultForwarder} that will forward a bugreport on
     * each failed test.
     */
    private static class FailedTestBugreportGenerator extends ResultForwarder {
        private ITestDevice mDevice;

        public FailedTestBugreportGenerator(ITestInvocationListener listener, ITestDevice device) {
            super(listener);
            mDevice = device;
        }

        // 当ddmlib中ITestRunListener监听器触发testFailed方法时,该方法被调用.ITestRunListener的方法是由ddmlib内部调用的
        // 该方法将在android-cts\repository\logs生成bugreport信息的文本文件并打包
        @Override
        public void testFailed(TestFailure status, TestIdentifier test, String trace) {
            super.testFailed(status, test, trace);
            InputStreamSource bugSource = mDevice.getBugreport();
            // System.out.println("CtsTest.FailedTestBugreportGenerator.testFailed: 执行case失败,需要保存当前bugreport的信息");
            // CLog.logAndDisplay(LogLevel.INFO,
            // String.format("[testFailed] %s", test.toString()));
            super.testLog(String.format("bug-%s_%s", test.getClassName(), test.getTestName()), LogDataType.TEXT, bugSource);
            bugSource.cancel();
        }
    }

    /**
     * 保存system缓冲区的日志 A {@link ResultForwarder} that will forward a logcat
     * snapshot on each failed test.
     */
    private static class FailedTestLogcatGenerator extends ResultForwarder {
        private ITestDevice mDevice;
        private int mNumLogcatBytes;

        public FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device, int maxLogcatBytes) {
            super(listener);
            mDevice = device;
            mNumLogcatBytes = maxLogcatBytes;
        }

        @Override
        public void testFailed(TestFailure status, TestIdentifier test, String trace) {
            super.testFailed(status, test, trace);
            // sleep a small amount of time to ensure test failure stack trace
            // makes it into logcat
            // capture
            RunUtil.getDefault().sleep(10);
            InputStreamSource logSource = mDevice.getLogcat(mNumLogcatBytes);
            super.testLog(String.format("logcat-%s_%s", test.getClassName(), test.getTestName()), LogDataType.TEXT, logSource);
            logSource.cancel();
        }
    }

    /**
     * 保存截图 A {@link ResultForwarder} that will forward a screenshot on test
     * failures.
     */
    private static class FailedTestScreenshotGenerator extends ResultForwarder {
        private ITestDevice mDevice;

        public FailedTestScreenshotGenerator(ITestInvocationListener listener, ITestDevice device) {
            super(listener);
            mDevice = device;
        }

        @Override
        public void testFailed(TestFailure status, TestIdentifier test, String trace) {
            super.testFailed(status, test, trace);

            try {
                InputStreamSource screenSource = mDevice.getScreenshot();
                super.testLog(String.format("screenshot-%s_%s", test.getClassName(), test.getTestName()), LogDataType.PNG, screenSource);
                screenSource.cancel();
            } catch (DeviceNotAvailableException e) {
                // TODO: rethrow this somehow
                CLog.e("Device %s became unavailable while capturing screenshot, %s", mDevice.getSerialNumber(), e.toString());
            }
        }
    }

1.老大 FailedTestBugreportGenerator:保存 bugreport 信息。
2.老二 FailedTestLogcatGenerator:logcat 信息,system 缓冲区里的日志信息保存到本地。
3.老三 FailedTestScreenshotGenerator:截图,case 失败时保存第一现场的截图。


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