Przykład kompleksowego testu TF

W tym samouczku omawiamy proces tworzenia konfiguracji testowej „hello world” dla Trade Federation (TF). Przedstawiamy też praktyczne informacje na temat tej platformy. Zaczynając od środowiska programistycznego, utworzysz prostą konfigurację i dodasz funkcje.

Samouczek przedstawia proces tworzenia testu jako zestaw ćwiczeń, z których każde składa się z kilku kroków. Każdy z nich pokazuje, jak tworzyć i stopniowo ulepszać konfigurację. Udostępniamy cały przykładowy kod potrzebny do skonfigurowania testu, a tytuł każdego ćwiczenia jest opatrzony literą, która określa role zaangażowane w konkretnym kroku:

  • D – deweloper
  • I dla integratora
  • R – Test Runner

Po zakończeniu samouczka będziesz mieć działającą konfigurację TF i zrozumiesz wiele ważnych pojęć związanych z frameworkiem TF.

Konfigurowanie federacji handlowej

Szczegółowe informacje o konfigurowaniu środowiska rozwoju TF znajdziesz w artykule Konfigurowanie maszyny. W dalszej części tego samouczka zakładamy, że masz otwartą powłokę skonfigurowaną pod kątem środowiska TF.

W tym samouczku pokazujemy, jak dodać konfigurację i jej klasy do głównej biblioteki frameworka TF. Można to rozszerzyć na potrzeby tworzenia modułów poza drzewem źródłowym, kompilując plik JAR z zawartością, a potem kompilując swoje moduły z użyciem tego pliku.

Tworzenie testowej klasy (D)

Utwórz test „Hello World”, który po prostu wypisuje wiadomość na stdout. Test skompilowany z użyciem narzędzia MSBuild zwykle implementuje interfejs IRemoteTest. Oto implementacja 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!");
    }
}

Zapisz ten przykładowy kod w pliku <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.javai ponownie skompiluj tradefed w powłoce:

m -jN

Pamiętaj, że w tym przykładzie parametr CLog.i służy do kierowania danych na konsolę. Więcej informacji o rejestrowaniu w ramach wymiany danych znajdziesz w artykule Rejestrowanie (D, I, R).

Jeśli kompilacja się nie powiedzie, zapoznaj się z sekcją Konfiguracja maszyny, aby upewnić się, że nie pominięto żadnego kroku.

Tworzenie konfiguracji (I)

Testy Federation Trade są wykonywane przez utworzenie konfiguracji, czyli pliku XML, który instruuje narzędzie tradefed, które testy mają być wykonywane, a także które inne moduły mają być wykonywane i w jakiej kolejności.

Utwórz nową konfigurację dla HelloWorldTest (zwróć uwagę na pełną nazwę klasy HelloWorldTest):

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

Zapisz te dane w pliku helloworld.xml w dowolnym miejscu w lokalnym systemie plików (np. /tmp/helloworld.xml). TF przeanalizuje plik XML konfiguracji (zwany też config), załaduje określoną klasę za pomocą odbicia lustrzanego, tworzy jej instancję, przekształca ją w element typu IRemoteTest i wywołuje metodę run.

Uruchom konfigurację (R)

W powłoce uruchom konsolę tradefed:

tradefed.sh

Upewnij się, że urządzenie jest połączone z hostem i widoczne dla tradefed:

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

Konfiguracje można uruchamiać za pomocą polecenia run <config> w konsoli. Spróbuj:

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!

W terminalu powinien pojawić się komunikat „Hello, TF World!”.

Aby sprawdzić, czy polecenie zostało wykonane, użyj w prośbie konsoli polecenia list invocations lub l i. Nie powinno się nic wydrukować. Jeśli polecenia są obecnie wykonywane, wyświetlają się w ten sposób:

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}'

Dodaj konfigurację do classpath (D, I, R)

Dla wygody wdrażania możesz też umieścić konfiguracje w samych plikach JAR. Narzędzie Tradefed automatycznie rozpoznaje wszystkie konfiguracje umieszczone w folderach config w ścieżce klas.

Aby to zrobić, przenieś plik helloworld.xml do biblioteki podstawowej tradefed (<tree>/tools/tradefederation/core/res/config/example/helloworld.xml). Zbuduj ponownie tradefed, uruchom ponownie konsolę tradefed, a następnie poproś tradefed o wyświetlenie listy konfiguracji z ścieżki klas:

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

Teraz możesz uruchomić konfigurację helloworld za pomocą:

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!

Interakcja z urządzeniem (D, R)

Do tej pory nasz HelloWorldTest nie robi nic interesującego. Specjalnością Tradefed jest przeprowadzanie testów na urządzeniach z Androidem, więc dodaj do testu urządzenie z Androidem.

Testy mogą uzyskać odwołanie do urządzenia z Androidem za pomocą funkcji TestInformation, która jest udostępniana przez platformę podczas wywołania metody IRemoteTest#run.

Zmieńmy komunikat drukowania HelloWorldTest, aby wyświetlał numer seryjny urządzenia:

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

Teraz ponownie skompiluj plik tradefed i sprawdź listę urządzeń:

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

Zwróć uwagę na numer seryjny podany jako Dostępne. To urządzenie powinno zostać przypisane do 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

Powinien pojawić się nowy komunikat drukowania z numerem seryjnym urządzenia.

Wysyłanie wyników testu (D)

IRemoteTest przekazuje wyniki przez wywoływanie metod instancji ITestInvocationListener przekazanej do metody #run. Sam framework TF odpowiada za zgłaszanie początku (za pomocą metody ITestInvocationListener#invocationStarted) i końca (za pomocą metody ITestInvocationListener#invocationEnded) każdego wywołania.

Testowanie to logiczny zbiór testów. Aby zgłaszać wyniki testów, IRemoteTest odpowiada za zgłaszanie rozpoczęcia testu, rozpoczęcia i zakończenia każdego testu oraz zakończenia testu.

Oto jak może wyglądać implementacja HelloWorldTest z jednym wynikiem testu nieudanego.

@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 zawiera kilka implementacji IRemoteTest, których możesz używać wielokrotnie zamiast pisać własne od podstaw. Na przykład InstrumentationTest może zdalnie uruchamiać testy aplikacji na urządzeniu z Androidem, analizować wyniki i przekazywać je do ITestInvocationListener. Więcej informacji znajdziesz w sekcji Typy testów.

Wyniki testów w sklepie (I)

Domyślną implementacją odbiornika testów w przypadku konfiguracji TF jest TextResultReporter, który zapisuje wyniki wywołania w wyjściu standardowym. Aby to zilustrować, uruchom konfigurację HelloWorldTest z poprzedniej sekcji:

./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

Aby przechowywać wyniki wywołania w innym miejscu, np. w pliku, określ niestandardową implementację ITestInvocationListener za pomocą tagu result_reporter w konfiguracji.

TF zawiera też listenera XmlResultReporter, który zapisuje wyniki testów w pliku XML w formacie podobnym do używanego przez ant JUnit XML writer. Aby określić parametr result_reporter w konfiguracji, edytuj plik …/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>

Teraz ponownie utwórz plik tradefed i ponownie uruchom przykład „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

Zwróć uwagę na komunikat w logu, który informuje o wygenerowaniu pliku XML. Wygenerowany plik powinien wyglądać tak:

<?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>

Możesz też napisać własne niestandardowe interfejsy wywołania. Wystarczy, że zaimplementujesz interfejs ITestInvocationListener.

Tradefed obsługuje wielu odbiorców wywołania, dzięki czemu możesz wysyłać wyniki testów do wielu niezależnych miejsc docelowych. Aby to zrobić, w konfiguracji wskaż kilka tagów <result_reporter>.

Usługi rejestrowania (D, I, R)

Możliwości logowania w TF obejmują:

  1. Przechwytywanie dzienników z urządzenia (czyli logcat urządzenia)
  2. Dzienniki rejestrowane przez platformę Trade Federation działającą na komputerze hosta (tzw. dziennik hosta)

Ramka TF automatycznie rejestruje logcat z przypisanego urządzenia i przesyła go do komponentu wywołania w celu przetworzenia. XmlResultReporter następnie zapisuje zrzut ekranu logcat urządzenia jako plik.

Dzienniki hosta TF są zgłaszane za pomocą opakowania CLog dla klasy ddmlib Log. Przekształcimy poprzednie wywołanie System.out.println w HelloWorldTest w wywołanie CLog:

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

CLog obsługuje interpolację ciągu znaków bezpośrednio, podobnie jak String.format. Po ponownym utworzeniu i uruchomieniu TF powinien wyświetlić się komunikat log na wyjściu standardowym:

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

Domyślnie narzędzie tradefed wyprowadza komunikaty hosta do standardowego wyjścia. TF zawiera też implementację logowania, która zapisuje wiadomości do pliku: FileLogger. Aby dodać rejestrowanie plików, dodaj do konfiguracji tag logger, podając pełną nazwę klasy 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>

Teraz ponownie skompiluj i uruchom przykład „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
…

Komunikat logowania wskazuje ścieżkę do pliku logowania hosta, który po otwarciu powinien zawierać komunikat logowania HelloWorldTest:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Przykładowe dane wyjściowe:

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

Opcje obsługi (D, I, R)

Obiekty wczytane z konfiguracji TF (czyli obiekty konfiguracji) mogą też otrzymywać dane z argumentów wiersza poleceń za pomocą adnotacji @Option.

Aby uczestniczyć w tym procesie, klasa obiektu Configuration stosuje adnotację @Option do pola członka i nadaje mu niepowtarzalną nazwę. Dzięki temu wartość pola członka może być wypełniana za pomocą opcji wiersza poleceń (a także automatycznie dodawana do systemu pomocy konfiguracji).

Uwaga: nie wszystkie typy pól są obsługiwane. Opis obsługiwanych typów znajdziesz w interfejsie OptionSetter.

Dodajmy @Option do 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";

Następnie dodamy komunikat dziennika, aby wyświetlić wartość opcji w HelloWorldTest, abyśmy mogli sprawdzić, czy została ona prawidłowo odebrana:

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

Na koniec ponownie skompiluj TF i uruchom helloworld. Powinieneś zobaczyć wiadomość z wartością domyślną my_option:

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

Przekazywanie wartości z wiersza poleceń

Przekaż wartość dla zmiennej my_option. Zmienna my_option powinna zostać wypełniona tą wartością:

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

Konfiguracje TF zawierają też system pomocy, który automatycznie wyświetla tekst pomocy dla pól @Option. Wypróbuj to teraz. Powinien pojawić się tekst pomocy dla 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.

Zwróć uwagę na komunikat o „drukowaniu tylko ważnych opcji”. Aby ograniczyć natłok w informacjach o opcjach, TF używa atrybutu Option#importance, aby określić, czy należy wyświetlić tekst pomocy dotyczącego pola @Option, gdy podany jest atrybut --help. --help-all zawsze wyświetla pomoc dotyczącą wszystkich pól @Option, niezależnie od ich znaczenia. Więcej informacji znajdziesz w artykule Option.Importance.

Przekazywanie wartości z konfiguracji

Wartość opcji możesz też określić w konfiguracji, dodając element <option name="" value="">. Aby przetestować tę funkcję, wykonaj te czynności:helloworld.xml

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

Ponowne skompilowanie i uruchomienie helloworld powinno teraz wygenerować ten wynik:

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

Pomoc dotycząca konfiguracji powinna się zaktualizować, aby wskazywać domyślną wartość my_option:

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

Inne obiekty konfiguracji zawarte w konfiguracji helloworld, takie jak FileLogger, również obsługują opcje. Opcja --log-level-display jest interesująca, ponieważ filtruje dzienniki, które wyświetlają się na stdout. Wcześniej w tym samouczku mogłeś/mogłaś zauważyć stronę „Hello, TF World!”. Po przejściu na użycie FileLogger przestał się wyświetlać komunikat z logów „I have device…” na wyjściu standardowym. Możesz zwiększyć szczegółowość logowania na wyjście standardowe, podając argument --log-level-display.

Spróbuj teraz wykonać tę czynność. Komunikat logowania „I have device” powinien ponownie pojawić się w stdout, a oprócz tego zostanie zapisany w pliku:

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

To wszystko.

Pamiętaj, że jeśli utkniesz w jakimś miejscu, kod źródłowy Trade Federation zawiera wiele przydatnych informacji, które nie są dostępne w dokumentacji. Jeśli nic nie pomoże, spróbuj zapytać na forum android-platform w Google Groups, wpisując „Trade Federation” w temacie wiadomości.