Пример сквозного тестирования TF

В этом руководстве рассказывается, как создать тестовую конфигурацию Trade Federation (Tradefed или TF) "hello world" и как использовать фреймворк TF. Начните с создания простой конфигурации в среде разработки и добавьте функции.

В этом руководстве процесс разработки теста представлен в виде набора упражнений, каждое из которых состоит из нескольких шагов. Они показывают, как создать и постепенно улучшить конфигурацию. Вам будет предоставлен весь образец кода, необходимый для выполнения теста. Названия упражнений помечены буквами, обозначающими роли, которые должны быть задействованы на этом этапе:

  • D – разработчик (Developer);
  • I – интегратор;
  • R – Test Runner;

После завершения руководства у вас будет рабочая конфигурация TF и понимание многих важных концепций фреймворка TF.

Как настроить Trade Federation

Подробнее о том, как настроить среду разработки TF… В остальной части этого руководства предполагается, что у вас открыта оболочка, инициализированная в среде TF.

В этом руководстве показано, как добавить конфигурацию и ее классы в основную библиотеку фреймворка TensorFlow. Это можно расширить до разработки модулей за пределами дерева исходного кода, скомпилировав JAR-файл tradefed, а затем скомпилировав ваши модули с этим 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 и пересоберите tradefed из оболочки:

m -jN

Обратите внимание, что в приведенном выше примере CLog.i используется для вывода данных в консоль. Подробнее о регистрации в Trade Federation рассказывается в разделе Logging (D, I, R).

Если сборка не удалась, ознакомьтесь с разделом Настройка машины, чтобы убедиться, что вы не пропустили ни одного шага.

Как создать конфигурацию (I)

Тесты Trade Federation становятся исполняемыми путем создания конфигурации – 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)

Запустите консоль 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 или l i. Если ничего не выводится, значит команда выполнена. Если в данный момент выполняются команды, они будут показаны следующим образом:

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)

Для удобства развертывания вы также можете объединить конфигурации в сами JAR-файлы Tradefed. Tradefed автоматически распознает все конфигурации, размещенные в папках config в пути к классам.

Чтобы проиллюстрировать это, переместите файл 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, предоставленную фреймворком при вызове метода 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 сообщает о результатах, вызывая методы экземпляра ITestInvocationListener, предоставленного методу #run. Фреймворк 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. Подробнее о типах тестирования.

Результаты тестирования в магазине (I)

Реализация прослушивателя тестов по умолчанию для конфигурации 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

Чтобы сохранить результаты вызова в другом месте, например в файле, укажите собственную реализацию ITestInvocationListener, используя тег result_reporter в конфигурации.

TF также включает слушателя XmlResultReporter, который записывает результаты тестирования в XML-файл в формате, похожем на тот, который используется в ant JUnit XML writer. Чтобы указать result_reporter в конфигурации, измените …/res/config/example/helloworld.xmlconfig:

<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, а затем сохраняет журнал устройства в виде файла.

Журналы хоста TF создаются с помощью оболочки CLog для класса Log библиотеки ddmlib. Давайте преобразуем предыдущий вызов System.out.println в HelloWorldTest в вызов CLog:

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

CLog обрабатывает интерполяцию строк напрямую, как и String.format. После пересборки и повторного запуска TF вы должны увидеть следующее сообщение в журнале в stdout:

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.

Добавьте @Option в HelloWorldTest:

@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, чтобы определить, показывать ли текст справки для определенного поля @Option, когда указан параметр --help. --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 интересен тем, что он фильтрует журналы, которые появляются в stdout. Ранее в этом руководстве вы могли заметить, что в файле hello_world.py Сообщение в журнале "I have device …" перестало отображаться в stdout после того, как мы перешли на использование FileLogger. Вы можете увеличить детализацию ведения журнала в stdout, передав аргумент --log-level-display.

Попробуйте сделать это сейчас, и вы увидите, что сообщение в журнале "I have device" снова появится в stdout, а также будет записано в файл:

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

Вот и все!

Если вы столкнулись с проблемой, помните, что в исходном коде Trade Federation есть много полезной информации, которая не представлена в документации. Если ничего не помогает, попробуйте задать вопрос в группе Google "Платформа Android", указав в теме письма "Торговая федерация".