端到端 TF 測試示例

本教程將指導您創建“hello world”Trade Federation (TF) 測試配置,並向您介紹 TF 框架。從開發環境開始,您將創建一個簡單的配置並添加功能。

本教程將測試開發過程呈現為一組練習,每個練習都包含幾個步驟,演示如何構建和逐步完善您的配置。提供了完成測試配置所需的所有示例代碼,並且每個練習的標題都帶有一個描述該步驟中涉及的角色的字母:

  • D開發人員
  • 為積分器
  • R代表測試運行器

完成本教程後,您將擁有一個正常運行的 TF 配置,並了解 TF 框架中的許多重要概念。

成立貿易聯盟

有關設置 TF 開發環境的詳細信息,請參閱機器設置。本教程的其餘部分假設您打開了一個已初始化為 TF 環境的 shell。

為簡單起見,本教程演示瞭如何將配置及其類添加到 TF 框架核心庫。通過編譯 tradefed JAR,然後針對該 JAR 編譯您的模塊,這可以擴展到在源代碼樹之外開發模塊。

創建測試類 (D)

讓我們創建一個 hello world 測試,它只是將消息轉儲到標準輸出。 tradefed 測試通常實現IRemoteTest接口。這是 HelloWorldTest 的實現:

package com.android.tradefed.example;

import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IRemoteTest;

public class HelloWorldTest implements IRemoteTest {
    @Override
    public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
        CLog.i("Hello, TF World!");
    }
}

將此示例代碼保存到<tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java並從您的 shell 重建 tradefed:

m -jN

請注意,上面示例中的CLog.i用於將輸出定向到控制台。登錄 (D, I, R)中描述了有關登錄 Trade Federation 的更多信息。

如果構建不成功,請諮詢機器設置以確保您沒有錯過任何步驟。

創建配置(一)

Trade Federation 測試通過創建Configuration來執行,這是一個 XML 文件,指示 tradefed 運行哪個(或多個)測試,以及要執行的其他模塊以及以什麼順序執行。

讓我們為 HelloWorldTest 創建一個新配置(注意 HelloWorldTest 的完整類名):

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
</configuration>

將此數據保存到本地文件系統上任何位置的helloworld.xml文件中(例如/tmp/helloworld.xml )。 TF 將解析配置 XML 文件(又名config ),使用反射加載指定的類,實例化它,將其轉換為IRemoteTest ,並調用其run方法。

運行配置(R)

在您的 shell 中,啟動 tradefed 控制台:

tradefed.sh

確保設備已連接到主機並且對 tradefed 可見:

tf> list devices
Serial            State      Product   Variant   Build   Battery
004ad9880810a548  Available  mako      mako      JDQ39   100

可以使用run <config>控制台命令執行配置。嘗試:

tf> run /tmp/helloworld.xml
05-12 13:19:36 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World!

您應該會看到“Hello,TF World!”在終端上輸出。

您可以通過在控制台提示符中使用list invocationsli來確認命令已完成運行,並且它應該不打印任何內容。如果命令當前正在運行,它們將顯示如下:

tf >l i
Command Id  Exec Time  Device       State
10          0m:00      [876X00GNG]  running stub on build(s) 'BuildInfo{bid=0, target=stub, serial=876X00GNG}'

將配置添加到類路徑(D、I、R)

為了方便部署,您還可以將配置捆綁到 tradefed JAR 本身。 Tradefed 自動識別放置在類路徑上的配置文件夾中的所有配置。

為了說明,將helloworld.xml文件移動到 tradefed 核心庫 ( <tree>/tools/tradefederation/core/res/config/example/helloworld.xml )。重建 tradefed,重啟 tradefed 控制台,然後讓 tradefed 顯示類路徑中的配置列表:

tf> list configs
[…]
example/helloworld: Runs the hello world test

您現在可以使用以下命令運行 helloworld 配置:

tf> run example/helloworld
05-12 13:21:21 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World!

與設備交互(D、R)

到目前為止,我們的 HelloWorldTest 沒有做任何有趣的事情。 Tradefed 的專長是使用 Android 設備運行測試,因此讓我們將 Android 設備添加到測試中。

測試可以通過調用IRemoteTest#run方法時由框架提供的TestInformation獲取對 Android 設備的引用。

讓我們修改 HelloWorldTest 打印消息以顯示設備的序列號:

@Override
public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device " + testInfo.getDevice().getSerialNumber());
}

現在重建 tradefed 並檢查設備列表:

tradefed.sh
tf> list devices
Serial            State      Product   Variant   Build   Battery
004ad9880810a548  Available  mako      mako      JDQ39   100

記下列為可用的序列號;那就是應該分配給 HelloWorld 的設備:

tf> run example/helloworld
05-12 13:26:18 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World! I have device 004ad9880810a548

您應該會看到顯示設備序列號的新打印消息。

發送測試結果 (D)

IRemoteTest通過調用提供給#run方法的ITestInvocationListener實例上的方法來報告結果。 TF 框架本身負責報告每個調用的開始(通過ITestInvocationListener#invocationStarted )和結束(通過ITestInvocationListener#invocationEnded )。

測試運行是測試的邏輯集合。為了報告測試結果, IRemoteTest負責報告測試運行的開始、每個測試的開始和結束以及測試運行的結束。

這是 HelloWorldTest 實現在單個失敗測試結果時可能看起來的樣子。

@Override
public void run(TestInformation testInfo, ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device " + testInfo.getDevice().getSerialNumber());

    TestDescription testId = new TestDescription("com.example.TestClassName", "sampleTest");
    listener.testRunStarted("helloworldrun", 1);
    listener.testStarted(testId);
    listener.testFailed(testId, "oh noes, test failed");
    listener.testEnded(testId, Collections.emptyMap());
    listener.testRunEnded(0, Collections.emptyMap());
}

TF 包含幾個可以重用的IRemoteTest實現,而不是從頭開始編寫自己的實現。例如, InstrumentationTest可以在 Android 設備上遠程運行 Android 應用程序的測試,解析結果,並將這些結果轉發給ITestInvocationListener )。有關詳細信息,請參閱測試類型

存儲測試結果(一)

TF 配置的默認測試偵聽器實現是TextResultReporter ,它將調用結果轉儲到標準輸出。為了說明,運行上一節中的 HelloWorldTest 配置:

./tradefed.sh
tf> run example/helloworld
04-29 18:25:55 I/TestInvocation: Invocation was started with cmd: /tmp/helloworld.xml
04-29 18:25:55 I/TestInvocation: Starting invocation for 'stub' with '[ BuildInfo{bid=0, target=stub, serial=876X00GNG} on device '876X00GNG']
04-29 18:25:55 I/HelloWorldTest: Hello, TF World! I have device 876X00GNG
04-29 18:25:55 I/InvocationToJUnitResultForwarder: Running helloworldrun: 1 tests
04-29 18:25:55 W/InvocationToJUnitResultForwarder:
Test com.example.TestClassName#sampleTest failed with stack:
 oh noes, test failed
04-29 18:25:55 I/InvocationToJUnitResultForwarder: Run ended in 0 ms

要將調用結果存儲在其他位置(例如文件中),請使用配置中的result_reporter標記指定自定義ITestInvocationListener實現。

TF 還包括XmlResultReporter偵聽器,它將測試結果以類似於ant JUnit XML 編寫器使用的格式寫入 XML 文件。要在配置中指定 result_reporter,請編輯…/res/config/example/helloworld.xml配置:

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
</configuration>

現在重建 tradefed 並重新運行 hello world 示例:

tf> run example/helloworld
05-16 21:07:07 I/TestInvocation: Starting invocation for target stub on build 0 on device 004ad9880810a548
Hello, TF World! I have device 004ad9880810a548
05-16 21:07:07 I/XmlResultReporter: Saved device_logcat log to /tmp/0/inv_2991649128735283633/device_logcat_6999997036887173857.txt
05-16 21:07:07 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_2991649128735283633/host_log_6307746032218561704.txt
05-16 21:07:07 I/XmlResultReporter: XML test result file generated at /tmp/0/inv_2991649128735283633/test_result_536358148261684076.xml. Total tests 1, Failed 1, Error 0

請注意說明已生成 XML 文件的日誌消息;生成的文件應如下所示:

<?xml version='1.0' encoding='UTF-8' ?>
<testsuite name="stub" tests="1" failures="1" errors="0" time="9" timestamp="2011-05-17T04:07:07" hostname="localhost">
  <properties />
  <testcase name="sampleTest" classname="com.example.TestClassName" time="0">
    <failure>oh noes, test failed
    </failure>
  </testcase>
</testsuite>

您還可以編寫自己的自定義調用偵聽器——它們只需要實現ITestInvocationListener接口。

Tradefed 支持多個調用偵聽器,因此您可以將測試結果發送到多個獨立的目的地。為此,只需在配置中指定多個<result_reporter>標記。

日誌記錄(D、I、R)

TF 的日誌記錄功能包括:

  1. 從設備捕獲日誌(又名設備 logcat)
  2. 記錄主機上運行的 Trade Federation 框架的日誌(又名主機日誌)

TF 框架會自動從分配的設備中捕獲 logcat,並將其發送給調用監聽器進行處理。然後XmlResultReporter將捕獲的設備 logcat 保存為文件。

使用 ddmlib Log 類的CLog 包裝器報告 TF 主機日誌。讓我們將之前在 HelloWorldTest 中的System.out.println調用轉換為CLog調用:

@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    CLog.i("Hello, TF World! I have device %s", getDevice().getSerialNumber());

CLog直接處理字符串插值,類似於String.format 。當您重建並重新運行 TF 時,您應該會在標準輸出上看到日誌消息:

tf> run example/helloworld
…
05-16 21:30:46 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548
…

默認情況下,tradefed 將主機日誌消息輸出到 stdout 。 TF 還包括一個將消息寫入文件的日誌實現: FileLogger 。要添加文件日誌記錄,請在配置中添加一個logger標記,指定FileLogger的完整類名:

<configuration description="Runs the hello world test">
    <test class="com.android.tradefed.example.HelloWorldTest" />
    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
    <logger class="com.android.tradefed.log.FileLogger" />
</configuration>

現在,再次重建並運行 helloworld 示例:

tf >run example/helloworld
…
05-16 21:38:21 I/XmlResultReporter: Saved device_logcat log to /tmp/0/inv_6390011618174565918/device_logcat_1302097394309452308.txt
05-16 21:38:21 I/XmlResultReporter: Saved host_log log to /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt
…

日誌消息指示主機日誌的路徑,查看時應包含您的 HelloWorldTest 日誌消息:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

示例輸出:

…
05-16 21:38:21 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548

處理選項(D、I、R)

從 TF 配置加載的對象(又名配置對象)也可以通過使用@Option註釋從命令行參數接收數據。

為了參與,Configuration 對像類將@Option註釋應用於成員字段並為其提供唯一名稱。這使得該成員字段值可以通過命令行選項填充(並且還會自動將該選項添加到配置幫助系統中)。

注意:並非所有字段類型都受支持。有關支持的類型的描述,請參閱OptionSetter

讓我們向 HelloWorldTest 添加一個@Option

@Option(name="my_option",
        shortName='m',
        description="this is the option's help text",
        // always display this option in the default help text
        importance=Importance.ALWAYS)
private String mMyOption = "thisisthedefault";

接下來,讓我們添加一條日誌消息以顯示 HelloWorldTest 中選項的值,以便我們可以證明它已正確接收:

@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    …
    CLog.logAndDisplay(LogLevel.INFO, "I received option '%s'", mMyOption);

最後重建TF,運行helloworld;您應該會看到一條帶有my_option默認值的日誌消息:

tf> run example/helloworld
…
05-24 18:30:05 I/HelloWorldTest: I received option 'thisisthedefault'

從命令行傳遞值

my_option一個值;您應該看到my_option填充了該值:

tf> run example/helloworld --my_option foo
…
05-24 18:33:44 I/HelloWorldTest: I received option 'foo'

TF 配置還包括一個幫助系統,它會自動顯示@Option字段的幫助文本。立即嘗試,您應該會看到my_option的幫助文本:

tf> run example/helloworld --help
Printing help for only the important options. To see help for all options, use the --help-all flag

  cmd_options options:
    --[no-]help          display the help text for the most important/critical options. Default: false.
    --[no-]help-all      display the full help text for all options. Default: false.
    --[no-]loop          keep running continuously. Default: false.

  test options:
    -m, --my_option      this is the option's help text Default: thisisthedefault.

  'file' logger options:
    --log-level-display  the minimum log level to display on stdout. Must be one of verbose, debug, info, warn, error, assert. Default: error.

請注意有關“僅打印重要選項”的消息。為了減少選項幫助的混亂,TF 使用Option#importance屬性來確定是否在指定--help時顯示特定的@Option字段幫助文本。 --help-all始終顯示所有@Option字段的幫助,無論其重要性如何。有關詳細信息,請參閱Option.Importance

從配置中傳遞值

您還可以通過添加<option name="" value="">元素在配置中指定選項值。使用helloworld.xml對其進行測試:

<test class="com.android.tradefed.example.HelloWorldTest" >
    <option name="my_option" value="fromxml" />
</test>

重新構建和運行 helloworld 現在應該會產生以下輸出:

05-24 20:38:25 I/HelloWorldTest: I received option 'fromxml'

配置幫助也應該更新以指示my_option的默認值:

tf> run example/helloworld --help
  test options:
    -m, --my_option      this is the option's help text Default: fromxml.

helloworld 配置中包含的其他配置對象,例如FileLogger ,也接受選項。選項--log-level-display很有趣,因為它過濾顯示在標準輸出上的日誌。在本教程的前面部分,您可能已經註意到“Hello,TF World!我有設備……”日誌消息在我們切換到使用FileLogger後停止顯示在標準輸出上。您可以通過傳入--log-level-display參數。

現在試試這個,除了被記錄到文件之外,您應該會在標準輸出上看到“我有設備”日誌消息:

tf> run example/helloworld --log-level-display info
…
05-24 18:53:50 I/HelloWorldTest: Hello, TF World! I have device 004ad9880810a548

就是這樣,伙計們!

提醒一下,如果您遇到問題, Trade Federation 源代碼包含許多文檔中未公開的有用信息。如果一切都失敗了,請嘗試在android-platform Google Group 上詢問消息主題中的“Trade Federation”。