Ví dụ về kiểm thử TF toàn diện

Hướng dẫn này sẽ hướng dẫn bạn tạo cấu hình kiểm thử "xin chào thế giới" cho Liên minh thương mại (Tradefed hoặc TF) và giới thiệu thực tế về khung TF. Bắt đầu từ một môi trường phát triển, bạn sẽ tạo một cấu hình đơn giản và thêm các tính năng.

Hướng dẫn này trình bày quy trình phát triển kiểm thử dưới dạng một bộ bài tập, mỗi bài tập bao gồm một số bước minh hoạ cách tạo và dần dần tinh chỉnh cấu hình. Tất cả mã mẫu bạn cần để hoàn tất cấu hình kiểm thử đều được cung cấp và tiêu đề của mỗi bài tập được chú thích bằng một chữ cái mô tả các vai trò liên quan trong bước đó:

  • D cho Nhà phát triển
  • I cho Trình tích hợp
  • R cho Trình chạy kiểm thử

Sau khi hoàn tất hướng dẫn này, bạn sẽ có một cấu hình TF hoạt động và hiểu được nhiều khái niệm quan trọng trong khung TF.

Thiết lập Liên minh thương mại

Để biết thông tin chi tiết về cách thiết lập môi trường phát triển TF, hãy xem phần Thiết lập máy. Phần còn lại của hướng dẫn này giả định rằng bạn đã mở một shell đã được khởi chạy cho môi trường TF.

Để đơn giản, hướng dẫn này minh hoạ cách thêm một cấu hình và các lớp của cấu hình đó vào thư viện cốt lõi của khung TF. Bạn có thể mở rộng tính năng này để phát triển các mô-đun bên ngoài cây nguồn bằng cách biên dịch tệp JAR được phân phối thương mại, sau đó biên dịch các mô-đun của bạn dựa trên tệp JAR đó.

Tạo lớp kiểm thử (D)

Hãy tạo một chương trình kiểm thử hello world chỉ kết xuất một thông báo vào stdout. Quy trình kiểm thử được cung cấp thường triển khai giao diện IRemoteTest. Dưới đây là cách triển khai 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!");
    }
}

Lưu mã mẫu này vào <tree>/tools/tradefederation/core/src/com/android/tradefed/example/HelloWorldTest.java và tạo lại tradefed từ shell:

m -jN

Xin lưu ý rằng CLog.i trong ví dụ trên được dùng để chuyển hướng đầu ra đến bảng điều khiển. Thông tin khác về cách đăng nhập vào Trade Federation được mô tả trong phần Ghi nhật ký (D, I, R).

Nếu bản dựng không thành công, hãy tham khảo phần Thiết lập máy để đảm bảo bạn không bỏ lỡ bước nào.

Tạo cấu hình (I)

Bạn có thể thực thi các kiểm thử của Trade Federation bằng cách tạo một Cấu hình (Configuration). Đây là một tệp XML hướng dẫn Tradefed về kiểm thử (hoặc các kiểm thử) cần chạy, cũng như các mô-đun khác cần thực thi và thứ tự thực thi.

Hãy tạo một Cấu hình mới cho HelloWorldTest (lưu ý tên lớp đầy đủ của HelloWorldTest):

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

Lưu dữ liệu này vào tệp helloworld.xml ở bất kỳ đâu trên hệ thống tệp cục bộ (ví dụ: /tmp/helloworld.xml). TF sẽ phân tích cú pháp tệp XML Cấu hình (còn gọi là config), tải lớp đã chỉ định bằng cách phản chiếu, tạo bản sao lớp đó, truyền lớp đó vào IRemoteTest và gọi phương thức run của lớp đó.

Chạy cấu hình (R)

Trên shell, hãy khởi chạy bảng điều khiển tradefed:

tradefed.sh

Đảm bảo thiết bị được kết nối với máy chủ lưu trữ và hiển thị với tradefed:

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

Bạn có thể thực thi cấu hình bằng lệnh bảng điều khiển run <config>. Hãy thử:

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!

Bạn sẽ thấy kết quả "Hello, TF World!" (Xin chào, TF World!) trên thiết bị đầu cuối.

Bạn có thể xác nhận rằng một lệnh đã chạy xong bằng cách sử dụng list invocations hoặc l i trong lời nhắc của bảng điều khiển và lệnh này sẽ không in ra gì cả. Nếu các lệnh đang chạy, thì các lệnh đó sẽ hiển thị như sau:

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

Thêm cấu hình vào đường dẫn lớp (D, I, R)

Để thuận tiện cho việc triển khai, bạn cũng có thể gói các cấu hình vào chính tệp JAR của tradefed. Tradefed tự động nhận dạng tất cả cấu hình được đặt trong thư mục config trên đường dẫn lớp.

Để minh hoạ, hãy di chuyển tệp helloworld.xml vào thư viện cốt lõi của tradefed (<tree>/tools/tradefederation/core/res/config/example/helloworld.xml). Tạo lại tradefed, khởi động lại bảng điều khiển tradefed, sau đó yêu cầu tradefed hiển thị danh sách cấu hình từ đường dẫn lớp:

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

Giờ đây, bạn có thể chạy cấu hình helloworld bằng cách sử dụng:

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!

Tương tác với thiết bị (D, R)

Cho đến nay, HelloWorldTest của chúng ta chưa làm được gì thú vị. Chuyên môn của Tradefed là chạy kiểm thử bằng các thiết bị Android, vì vậy, hãy thêm một thiết bị Android vào kiểm thử.

Các chương trình kiểm thử có thể tham chiếu đến một thiết bị Android bằng cách sử dụng TestInformation do khung cung cấp khi phương thức IRemoteTest#run được gọi.

Hãy sửa đổi thông báo in HelloWorldTest để hiển thị số sê-ri của thiết bị:

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

Bây giờ, hãy tạo lại tradefed và kiểm tra danh sách thiết bị:

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

Hãy ghi lại số sê-ri được liệt kê là Có sẵn; đó là thiết bị cần được phân bổ cho 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

Bạn sẽ thấy thông báo in mới hiển thị số sê-ri của thiết bị.

Gửi kết quả kiểm thử (D)

IRemoteTest báo cáo kết quả bằng cách gọi các phương thức trên thực thể ITestInvocationListener được cung cấp cho phương thức #run. Bản thân khung TF chịu trách nhiệm báo cáo thời điểm bắt đầu (thông qua ITestInvocationListener#invocationStarted) và kết thúc (thông qua ITestInvocationListener#invocationEnded) của mỗi Lệnh gọi.

Chạy kiểm thử là một tập hợp các kiểm thử hợp lý. Để báo cáo kết quả kiểm thử, IRemoteTest chịu trách nhiệm báo cáo thời điểm bắt đầu chạy kiểm thử, thời điểm bắt đầu và kết thúc mỗi lần kiểm thử cũng như thời điểm kết thúc chạy kiểm thử.

Sau đây là cách triển khai HelloWorldTest với một kết quả kiểm thử không thành công.

@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 bao gồm một số cách triển khai IRemoteTest mà bạn có thể sử dụng lại thay vì viết từ đầu. Ví dụ: InstrumentationTest có thể chạy các chương trình kiểm thử của ứng dụng Android từ xa trên một thiết bị Android, phân tích cú pháp kết quả và chuyển tiếp các kết quả đó đến ITestInvocationListener). Để biết thông tin chi tiết, hãy xem Các loại kiểm thử.

Lưu trữ kết quả kiểm thử (I)

Phương thức triển khai trình nghe kiểm thử mặc định cho cấu hình TF là TextResultReporter. Phương thức này sẽ kết xuất kết quả của lệnh gọi vào stdout. Để minh hoạ, hãy chạy cấu hình HelloWorldTest từ phần trước:

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

Để lưu trữ kết quả của lệnh gọi ở nơi khác, chẳng hạn như trong tệp, hãy chỉ định cách triển khai ITestInvocationListener tuỳ chỉnh bằng cách sử dụng thẻ result_reporter trong cấu hình.

TF cũng bao gồm trình nghe XmlResultReporter. Trình nghe này sẽ ghi kết quả kiểm thử vào tệp XML theo định dạng tương tự như định dạng mà trình ghi XML JUnit ant sử dụng. Để chỉ định result_reporter trong cấu hình, hãy chỉnh sửa cấu hình …/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>

Bây giờ, hãy tạo lại tradefed và chạy lại mẫu 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

Lưu ý thông báo nhật ký cho biết tệp XML đã được tạo; tệp đã tạo sẽ có dạng như sau:

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

Bạn cũng có thể tự viết trình nghe lệnh gọi tuỳ chỉnh – các trình nghe này chỉ cần triển khai giao diện ITestInvocationListener.

Tradefed hỗ trợ nhiều trình nghe lệnh gọi, vì vậy, bạn có thể gửi kết quả kiểm thử đến nhiều đích đến độc lập. Để làm việc này, bạn chỉ cần chỉ định nhiều thẻ <result_reporter> trong cấu hình.

Cơ sở ghi nhật ký (D, I, R)

Các cơ sở ghi nhật ký của TF bao gồm khả năng:

  1. Ghi nhật ký từ thiết bị (còn gọi là logcat thiết bị)
  2. Ghi nhật ký từ khung Trade Federation chạy trên máy chủ lưu trữ (còn gọi là nhật ký máy chủ lưu trữ)

Khung TF tự động chụp logcat từ thiết bị được phân bổ và gửi logcat đó đến trình nghe lệnh gọi để xử lý. Sau đó, XmlResultReporter sẽ lưu logcat thiết bị đã chụp dưới dạng tệp.

Nhật ký máy chủ lưu trữ TF được báo cáo bằng cách sử dụng trình bao bọc CLog cho lớp Nhật ký ddmlib. Hãy chuyển đổi lệnh gọi System.out.println trước đó trong HelloWorldTest thành lệnh gọi CLog:

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

CLog xử lý trực tiếp hoạt động nội suy chuỗi, tương tự như String.format. Khi tạo lại và chạy lại TF, bạn sẽ thấy thông báo nhật ký trên stdout:

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

Theo mặc định, tradefed xuất thông điệp nhật ký máy chủ sang stdout. TF cũng bao gồm một phương thức triển khai nhật ký ghi thông báo vào một tệp: FileLogger. Để thêm tính năng ghi nhật ký tệp, hãy thêm thẻ logger vào cấu hình, chỉ định tên lớp đầy đủ của 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>

Bây giờ, hãy tạo lại và chạy lại ví dụ 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
…

Thông báo nhật ký cho biết đường dẫn của nhật ký máy chủ. Khi xem, thông báo này phải chứa thông báo nhật ký HelloWorldTest:

more /tmp/0/inv_6390011618174565918/host_log_4255420317120216614.txt

Kết quả điểm dữ liệu:

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

Xử lý các tuỳ chọn (D, I, R)

Các đối tượng được tải từ Cấu hình TF (còn gọi là Đối tượng cấu hình) cũng có thể nhận dữ liệu từ các đối số dòng lệnh thông qua việc sử dụng chú giải @Option.

Để tham gia, lớp đối tượng Cấu hình sẽ áp dụng chú thích @Option cho trường thành viên và cung cấp tên riêng biệt cho trường đó. Thao tác này cho phép điền giá trị trường thành viên đó thông qua tuỳ chọn dòng lệnh (và cũng tự động thêm tuỳ chọn đó vào hệ thống trợ giúp cấu hình).

Lưu ý: Không phải loại trường nào cũng được hỗ trợ. Để biết nội dung mô tả về các loại được hỗ trợ, hãy xem OptionSetter.

Hãy thêm @Option vào 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";

Tiếp theo, hãy thêm một thông điệp nhật ký để hiển thị giá trị của tuỳ chọn trong HelloWorldTest để chúng ta có thể chứng minh rằng thông điệp đã được nhận chính xác:

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

Cuối cùng, hãy tạo lại TF và chạy helloworld; bạn sẽ thấy thông điệp nhật ký có giá trị mặc định my_option:

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

Truyền giá trị từ dòng lệnh

Truyền vào một giá trị cho my_option; bạn sẽ thấy my_option được điền sẵn giá trị đó:

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

Cấu hình TF cũng bao gồm một hệ thống trợ giúp, tự động hiển thị văn bản trợ giúp cho các trường @Option. Hãy thử ngay và bạn sẽ thấy văn bản trợ giúp cho 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.

Lưu ý thông báo về việc "chỉ in các tuỳ chọn quan trọng". Để giảm bớt sự lộn xộn của văn bản trợ giúp về tuỳ chọn, TF sử dụng thuộc tính Option#importance để xác định xem có hiển thị văn bản trợ giúp về trường @Option cụ thể hay không khi chỉ định --help. --help-all luôn hiển thị thông tin trợ giúp cho tất cả các trường @Option, bất kể mức độ quan trọng. Để biết thông tin chi tiết, hãy xem Option.Importance.

Truyền giá trị từ một cấu hình

Bạn cũng có thể chỉ định giá trị Tuỳ chọn trong cấu hình bằng cách thêm phần tử <option name="" value="">. Kiểm thử bằng helloworld.xml:

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

Giờ đây, việc tạo lại và chạy helloworld sẽ tạo ra kết quả sau:

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

Phần trợ giúp về cấu hình cũng sẽ cập nhật để cho biết giá trị mặc định của my_option:

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

Các đối tượng cấu hình khác có trong cấu hình helloworld, chẳng hạn như FileLogger, cũng chấp nhận các tuỳ chọn. Tuỳ chọn --log-level-display rất thú vị vì nó lọc các nhật ký xuất hiện trên stdout. Ở phần đầu của hướng dẫn này, bạn có thể đã nhận thấy thông báo "Hello, TF World! Thông báo nhật ký "I have device …" (Tôi có thiết bị …) đã ngừng hiển thị trên stdout sau khi chúng ta chuyển sang sử dụng FileLogger. Bạn có thể tăng mức độ chi tiết của việc ghi nhật ký vào stdout bằng cách truyền vào đối số --log-level-display.

Hãy thử ngay và bạn sẽ thấy thông báo nhật ký "I have device" (Tôi có thiết bị) xuất hiện trở lại trên stdout, ngoài việc được ghi nhật ký vào một tệp:

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

Vậy là xong!

Xin lưu ý rằng nếu bạn gặp khó khăn, mã nguồn của Liên minh thương mại có nhiều thông tin hữu ích không được trình bày trong tài liệu. Nếu không có cách nào khác, hãy thử hỏi trên Nhóm Google android-platform, với tiêu đề thư là "Trade Federation" (Liên minh thương mại).