端對端 TF 測試範例

本教學將引導您建立「hello world」Trade Federation(Tradefed 或 TF)測試配置,並為您提供 TF 框架的實際操作介紹。從開發環境開始,您將建立簡單的配置並新增功能。

本教學以一組練習的形式介紹了測試開發流程,每個練習都包含幾個步驟,示範如何建立並逐步完善您的配置。提供了完成測試配置所需的所有範例程式碼,並且每個練習的標題都用描述該步驟中涉及的角色的字母註釋:

  • D代表開發者
  • I代表積分器
  • 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用於將輸出定向到控制台。有關登入 Trade Federation 的更多信息,請參閱日誌記錄 (D、I、R)

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

建立配置(一)

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 invocations或控制台提示符號中的li來確認命令已完成運行,並且它不應列印任何內容。如果命令目前正在運行,則顯示如下:

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 裝置新增至測試。

測試可以使用TestInformation取得 Android 裝置的引用,該引用由框架在呼叫IRemoteTest#run方法時提供。

讓我們修改 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方法的ITestInitationListener實例上的方法來報告結果。 TF 框架本身負責報告每個呼叫的開始(透過ITestInvocableListener#invocateStarted )和結束(透過ITestInitationListener#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 ,它將呼叫結果轉儲到 stdout。為了說明這一點,請執行上一節的 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偵聽器,它將測試結果寫入 XML 文件,其格式類似於ant JUnit 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>

您也可以編寫自己的自訂呼叫偵聽器 - 它們只需要實作ITestInvocableListener介面即可。

Tradefed 支援多個呼叫偵聽器,因此您可以將測試結果傳送至多個獨立的目的地。為此,只需在配置中指定多個<result_reporter>標記即可。

測井設施(D、I、R)

TF 的日誌記錄設施具有以下能力:

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

TF框架會自動從已指派的裝置中擷取logcat並將其傳送至呼叫監聽器進行處理。然後, XmlResultReporter將捕獲的裝置 logcat 儲存為檔案。

TF 主機日誌使用 ddmlib Log 類別的CLog 包裝器進行報告。讓我們將 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註解從命令列參數接收資料。

為了參與,配置物件類別將@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很有趣,因為它過濾標準輸出上顯示的日誌。在本教學的前面,您可能已經注意到,在我們切換到使用FileLogger後,「您好,TF World!我有裝置...」日誌訊息不再顯示在 stdout 上。您可以透過傳入--log-level-display來增加記錄到stdout 的詳細程度--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

就這樣,夥計們!

提醒一下,如果您遇到困難,貿易聯盟原始碼有很多文件中未公開的有用資訊。如果所有其他方法都失敗,請嘗試在Android 平台Google 群組上提問,並在郵件主題中註明「Trade Federation」。