Exemple de test TF de bout en bout

Ce didacticiel vous guide dans la création d'une configuration de test « hello world » pour une fédération commerciale (Tradefed ou TF) et vous donne une introduction pratique au framework TF. À partir d’un environnement de développement, vous créerez une configuration simple et ajouterez des fonctionnalités.

Le didacticiel présente le processus de développement de tests sous la forme d'un ensemble d'exercices, chacun composé de plusieurs étapes, qui montrent comment créer et affiner progressivement votre configuration. Tous les exemples de code dont vous avez besoin pour terminer la configuration du test sont fournis et le titre de chaque exercice est annoté d'une lettre décrivant les rôles impliqués dans cette étape :

  • D pour développeur
  • I pour Intégrateur
  • R pour Test Runner

Après avoir terminé le didacticiel, vous disposerez d’une configuration TF fonctionnelle et comprendrez de nombreux concepts importants du framework TF.

Créer une fédération commerciale

Pour plus de détails sur la configuration de l'environnement de développement TF, voir Configuration de la machine . Le reste de ce didacticiel suppose que vous disposez d’un shell ouvert qui a été initialisé dans l’environnement TF.

Pour plus de simplicité, ce didacticiel illustre l'ajout d'une configuration et de ses classes à la bibliothèque principale du framework TF. Cela peut être étendu au développement de modules en dehors de l'arborescence source en compilant le JAR échangé, puis en compilant vos modules par rapport à ce JAR.

Créer une classe de test (D)

Créons un test Hello World qui envoie simplement un message sur la sortie standard. Un test tradefed implémente généralement l'interface IRemoteTest . Voici une implémentation pour 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!");
    }
}

Enregistrez cet exemple de code dans <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java et reconstruisez tradefed à partir de votre shell :

m -jN

Notez que CLog.i dans l'exemple ci-dessus est utilisé pour diriger la sortie vers la console. Plus d'informations sur la connexion à la Fédération du commerce sont décrites dans Logging (D, I, R) .

Si la génération échoue, consultez la configuration de la machine pour vous assurer que vous n'avez manqué aucune étape.

Créer une configuration (I)

Les tests de Trade Federation sont rendus exécutables en créant une Configuration , un fichier XML qui indique à tradefed quel test (ou tests) exécuter, ainsi que quels autres modules exécuter et dans quel ordre.

Créons une nouvelle configuration pour notre HelloWorldTest (notez le nom complet de la classe du HelloWorldTest) :

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

Enregistrez ces données dans un fichier helloworld.xml n'importe où sur votre système de fichiers local (par exemple /tmp/helloworld.xml ). TF analysera le fichier XML de configuration (alias config ), chargera la classe spécifiée à l'aide de la réflexion, l'instanciera, la convertira en IRemoteTest et appellera sa méthode run .

Exécutez la configuration (R)

Depuis votre shell, lancez la console tradefed :

tradefed.sh

Assurez-vous qu'un appareil est connecté à la machine hôte et est visible par tradefed :

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

Les configurations peuvent être exécutées à l'aide de la commande de console run <config> . Essayer:

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!

Vous devriez voir « Bonjour, TF World ! » sortie sur le terminal.

Vous pouvez confirmer qu'une commande est en cours d'exécution en utilisant list invocations ou li dans l'invite de la console, et elle ne devrait rien imprimer. Si des commandes sont en cours d'exécution, elles s'affichent comme suit :

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

Ajoutez la configuration au chemin de classe (D, I, R)

Pour faciliter le déploiement, vous pouvez également regrouper les configurations dans les JAR échangés eux-mêmes. Tradefed reconnaît automatiquement toutes les configurations placées dans les dossiers de configuration sur le chemin de classe.

Pour illustrer, déplacez le fichier helloworld.xml dans la bibliothèque principale de tradefed ( <tree>/tools/tradefederation/core/res/config/example/helloworld.xml ). Reconstruisez tradefed, redémarrez la console tradefed, puis demandez à tradefed d'afficher la liste des configurations du classpath :

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

Vous pouvez maintenant exécuter la configuration helloworld en utilisant :

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!

Interagir avec un appareil (D, R)

Jusqu'à présent, notre HelloWorldTest ne fait rien d'intéressant. La spécialité de Tradefed consiste à exécuter des tests à l'aide d'appareils Android, ajoutons donc un appareil Android au test.

Les tests peuvent obtenir une référence à un appareil Android en utilisant TestInformation , fournie par le framework lorsque la méthode IRemoteTest#run est appelée.

Modifions le message d'impression HelloWorldTest pour afficher le numéro de série de l'appareil :

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

Reconstruisez maintenant tradefed et vérifiez la liste des appareils :

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

Prenez note du numéro de série indiqué comme Disponible ; c'est l'appareil qui doit être alloué à 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

Vous devriez voir le nouveau message d'impression affichant le numéro de série de l'appareil.

Envoyer les résultats des tests (D)

IRemoteTest rapporte les résultats en appelant des méthodes sur l'instance ITestInvocationListener fournie à la méthode #run . Le framework TF lui-même est chargé de signaler le début (via ITestInvocationListener#invocationStarted ) et la fin (via ITestInvocationListener#invocationEnded ) de chaque invocation.

Une exécution de test est une collection logique de tests. Pour signaler les résultats des tests, IRemoteTest est chargé de signaler le début d'une exécution de test, le début et la fin de chaque test, ainsi que la fin de l'exécution de test.

Voici à quoi pourrait ressembler l’implémentation de HelloWorldTest avec un seul résultat de test échoué.

@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 inclut plusieurs implémentations IRemoteTest que vous pouvez réutiliser au lieu d'écrire la vôtre à partir de zéro. Par exemple, InstrumentationTest peut exécuter les tests d'une application Android à distance sur un appareil Android, analyser les résultats et transmettre ces résultats à ITestInvocationListener ). Pour plus de détails, voir Types de tests .

Stocker les résultats des tests (I)

L'implémentation par défaut de l'écouteur de test pour une configuration TF est TextResultReporter , qui transfère les résultats d'un appel vers la sortie standard. Pour illustrer, exécutez la configuration HelloWorldTest de la section précédente :

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

Pour stocker les résultats d'un appel ailleurs, par exemple dans un fichier, spécifiez une implémentation ITestInvocationListener personnalisée à l'aide de la balise result_reporter dans votre configuration.

TF inclut également l'écouteur XmlResultReporter , qui écrit les résultats des tests dans un fichier XML dans un format similaire à celui utilisé par l'écrivain XML Ant JUnit. Pour spécifier le result_reporter dans la configuration, modifiez la configuration …/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>

Maintenant, reconstruisez Tradefed et réexécutez l'exemple 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

Notez le message de journal indiquant qu'un fichier XML a été généré ; le fichier généré devrait ressembler à ceci :

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

Vous pouvez également écrire vos propres écouteurs d'appel personnalisés : ils doivent simplement implémenter l'interface ITestInvocationListener .

Tradefed prend en charge plusieurs écouteurs d'appel, vous pouvez donc envoyer les résultats des tests à plusieurs destinations indépendantes. Pour ce faire, spécifiez simplement plusieurs balises <result_reporter> dans votre configuration.

Installations forestières (D, I, R)

Les installations de journalisation de TF incluent la capacité de :

  1. Capturer les journaux de l'appareil (alias périphérique logcat)
  2. Enregistrez les journaux du framework Trade Federation exécuté sur la machine hôte (alias journal de l'hôte)

Le framework TF capture automatiquement le logcat du périphérique alloué et l'envoie à l'écouteur d'invocation pour traitement. XmlResultReporter enregistre ensuite le logcat de périphérique capturé sous forme de fichier.

Les journaux de l'hôte TF sont signalés à l'aide du wrapper CLog pour la classe ddmlib Log. Convertissons l'appel System.out.println précédent dans HelloWorldTest en un appel CLog :

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

CLog gère directement l'interpolation de chaîne, similaire à String.format . Lorsque vous reconstruisez et réexécutez TF, vous devriez voir le message de journal sur la sortie standard :

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

Par défaut, tradefed affiche les messages du journal de l'hôte sur stdout . TF inclut également une implémentation de journal qui écrit les messages dans un fichier : FileLogger . Pour ajouter la journalisation de fichiers, ajoutez une balise logger à la configuration, en spécifiant le nom de classe complet de 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>

Maintenant, reconstruisez et exécutez à nouveau l'exemple 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
…

Le message du journal indique le chemin du journal de l'hôte qui, une fois affiché, doit contenir votre message du journal HelloWorldTest :

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Exemple de sortie :

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

Options de manipulation (D, I, R)

Les objets chargés à partir d'une configuration TF (alias objets de configuration ) peuvent également recevoir des données à partir d'arguments de ligne de commande grâce à l'utilisation de l'annotation @Option .

Pour participer, une classe d'objets Configuration applique l'annotation @Option à un champ membre et lui fournit un nom unique. Cela permet à cette valeur de champ membre d'être renseignée via une option de ligne de commande (et ajoute également automatiquement cette option au système d'aide à la configuration).

Remarque : Tous les types de champs ne sont pas pris en charge. Pour une description des types pris en charge, consultez OptionSetter .

Ajoutons une @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";

Ensuite, ajoutons un message de journal pour afficher la valeur de l'option dans HelloWorldTest afin que nous puissions démontrer qu'elle a été reçue correctement :

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

Enfin, reconstruisez TF et exécutez helloworld ; vous devriez voir un message de journal avec la valeur par défaut my_option :

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

Transmettre les valeurs depuis la ligne de commande

Transmettez une valeur pour my_option ; vous devriez voir my_option renseigné avec cette valeur :

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

Les configurations TF incluent également un système d'aide, qui affiche automatiquement le texte d'aide pour les champs @Option . Essayez-le maintenant et vous devriez voir le texte d'aide pour 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.

Notez le message concernant "l'impression uniquement des options importantes". Pour réduire l'encombrement de l'aide sur les options, TF utilise l'attribut Option#importance pour déterminer s'il doit afficher un texte d'aide de champ @Option particulier lorsque --help est spécifié. --help-all affiche toujours l'aide pour tous les champs @Option , quelle que soit leur importance. Pour plus de détails, consultez Option.Importance .

Transmettre les valeurs d'une configuration

Vous pouvez également spécifier une valeur Option dans la configuration en ajoutant un élément <option name="" value=""> . Testez-le en utilisant helloworld.xml :

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

La reconstruction et l'exécution de helloworld devraient maintenant produire ce résultat :

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

L'aide à la configuration doit également être mise à jour pour indiquer la valeur par défaut de my_option :

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

D'autres objets de configuration inclus dans la configuration helloworld, tels que FileLogger , acceptent également des options. L'option --log-level-display est intéressante car elle filtre les logs qui s'affichent sur la sortie standard. Plus tôt dans le didacticiel, vous avez peut-être remarqué que le message de journal « Bonjour, TF World ! J'ai un appareil… » a cessé de s'afficher sur la sortie standard après que nous ayons utilisé FileLogger . Vous pouvez augmenter la verbosité de la journalisation sur la sortie standard en passant le --log-level-display argument --log-level-display .

Essayez ceci maintenant, et vous devriez voir le message de journal « J'ai un périphérique » réapparaître sur la sortie standard, en plus d'être connecté à un fichier :

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

C'est tout, les amis !

Pour rappel, si vous êtes bloqué sur quelque chose, le code source de la Trade Federation contient de nombreuses informations utiles qui ne sont pas exposées dans la documentation. Si tout le reste échoue, essayez de demander sur le groupe Google de la plateforme Android , avec « Trade Federation » dans le sujet du message.