TF 테스트 관련 종합 예시

이 가이드는 'Hello World' Trade Federation(Tradefed 또는 TF) 테스트 구성을 생성하는 과정을 안내하고 TF 프레임워크를 직접 체험해볼 수 있는 기회를 제공합니다. 개발 환경을 시작으로 단순한 구성을 생성하고 기능을 추가해 보세요.

이 가이드는 테스트 개발 프로세스를 여러 실습으로 보여줍니다. 각 실습은 구성을 빌드한 후 점차적으로 조정하는 방법을 보여주는 여러 단계로 이루어져 있습니다. 테스트 구성을 완료하는 데 필요한 모든 코드가 제공되며, 각 실습 제목에는 해당 단계와 관련된 역할을 설명하는 문자가 주석으로 달려 있습니다.

  • D: 개발자
  • I: 통합자
  • R: 테스트 실행자

이 가이드를 완료하면 온전히 작동하는 TF 구성을 얻고 TF 프레임워크의 수많은 중요 개념을 이해할 수 있게 됩니다.

Trade Federation 설정하기

TF 개발 환경 설정에 대한 자세한 내용은 시스템 설정을 참조하세요. 이 가이드의 나머지 부분에서는 TF 환경에 대해 초기화된 셸이 열려 있다고 가정합니다.

이 가이드는 사용자의 편의를 위해 구성 및 관련 클래스를 TF 프레임워크 핵심 라이브러리에 추가하는 과정을 보여줍니다. 이는 Tradefed JAR을 컴파일한 다음 해당 JAR에대한 모듈을 컴파일하여 소스 트리 외부에서의 모듈 개발로 확대 가능합니다.

테스트 클래스 만들기(D)

메시지를 stdout에 덤프하는 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>

이 데이터를 로컬 파일 시스템(예: /tmp/helloworld.xml)의 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}'

클래스 경로에 config 추가하기(D, I, R)

편리한 배포를 위해 config를 tradefed JAR 자체로 번들화할 수도 있습니다. 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 config을 실행할 수 있습니다.

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

Take note of the serial number listed as 사용 가능으로 나열된 일련번호를 확인합니다. 이는 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에 전달할 수 있습니다. 자세한 내용은 테스트 유형을 참조하세요.

테스트 결과 저장하기(I)

TF config의 기본적인 테스트 리스너 구현은 호출 결과를 stdbout에 덤프하는 TextResultReporter입니다. 이를 보여주려면 이전 섹션에서 HelloWorldTest config을 실행합니다.

./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는 테스트 결과를 ant JUnit XML 작성기에서 사용하는 유사한 형식의 XML 파일에 작성하는 XmlResultReporter 리스너도 포함합니다. 구성에 result_reporter를 지정하려면 …/res/config/example/helloworld.xml config을 수정합니다.

<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는 여러 호출 리스너를 지원하므로 테스트 결과를 여러 개별 도착지로 전송할 수 있습니다. 이를 수행하려면 config에 여러 개의 <result_reporter> 태그를 지정하기만 하면 됩니다.

시설 로깅(D, I, R)

TF의 로깅 기능에는 다음과 같은 기능도 포함되어 있습니다.

  1. 기기에서 로그를 캡처(기기 logcat)
  2. 호스트 시스템에서 실행 ㅈ중인 Trade Federation 프레임워크에서 로그를 기록(호스트 로그)

TF 프레임워크는 할당된 기기에서 자동으로 logcat을 캡처한 후 호출 리스너로 전송하여 처리하도록 합니다. 그러면 XmlResultReporter가 캡처된 기기 logcat을 파일로 저장합니다.

TF 호스트 로그는 ddmlib 로그 클래스의 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());

CLogString.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 태그를 config에 추가하여 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 속성을 사용하여 --help 지정 시 특정 @Option 필드의 도움말 텍스트를 표시할지 결정합니다. --help-all은 중요도와 상관없이 항상 모든 @Option 필드의 도움말을 표시합니다. 자세한 내용은 Option.Importance를 참조하세요.

구성의 값 전달하기

<option name="" value=""> 요소를 추가하여 config 내에 옵션 값을 지정할 수도 있습니다. 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 config에 포함된 FileLogger 등의 다른 구성 개체도 옵션을 수락해야 합니다. --log-level-display 옵션은 stdout에 표시되는 로그를 필터링한다는 점에서 흥미롭습니다. 가이드 초반에 'Hello, TF World!'를 봤을지도 모릅니다. FileLogger를 사용하여 전환한 후에는 '기기 있음…' 로그 메시지가 더 이상 stdout에 표시되지 않았습니다. --log-level-display 아그를 전달하여 stdout에 대한 로깅의 상세 출력을 높일 수 있습니다.

지금 시도해 보면 '기기 있음' 로그 메시지가 파일에 로그되는 것 외에 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 소스 코드에서 문서에 노출되지 않은 다수의 유용한 정보를 찾을 수 있습니다. 다른 모든 방법에 실패할 경우에는 android-platform Google Group에 문의하시기 바랍니다. 메시지 제목에 'Trade Federation'을 언급하세요.