シャードIRemoteTestテストランナーを作成する

テストランナーを作成するときは、スケーラビリティについて考えることが重要です。 「私のテストランナーが200Kのテストケースを実行しなければならなかったとしたら」、どれくらいの時間がかかりますか?

シャーディングは、通商連合で利用できる答えの1つです。ランナーが必要とするすべてのテストを、並列化できるいくつかのチャンクに分割する必要があります。

このページでは、Tradefedでランナーをシャーディング可能にする方法について説明します。

実装するインターフェース

TFによってシャーディング可能と見なされるように実装する最も重要なインターフェイスはIShardableTestで、これにはsplit(int numShard)split()の2つのメソッドが含まれています。

シャーディングが要求されたシャードの数に依存する場合は、 split(int numShard)を実装する必要があります。それ以外の場合は、 split()を実装します。

シャーディングパラメータ--shard-countおよび--shard-indexを使用してTFテストコマンドを実行すると、TFはすべてのIRemoteTestを反復処理して、 IShardableTestを実装しているものを探します。見つかった場合は、 splitを呼び出して、特定のシャードのテストケースのサブセットを実行するための新しいIRemoteTestオブジェクトを取得します。

分割実装について何を知っておくべきですか?

  • ランナーは、特定の条件でのみシャーディングする場合があります。その場合、シャーディングしなかったときにnullを返します。
  • 意味のある範囲で分割してみてください。ランナーを、意味のある実行単位に分割します。それは本当にあなたのランナーに依存します。次に例を示します。HostTestはクラスレベルでシャーディングされ、各テストクラスは個別のシャードに配置されます。
  • 理にかなっている場合は、シャーディングを少し制御するためのオプションをいくつか追加します。例: AndroidJUnitTestには、要求された数に関係なく、分割できるシャードの最大数を指定するajur-max-shardがあります。

詳細な実装例

参照できるIShardableTestを実装するサンプルコードスニペットを次に示します。完全なコードは(https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/master/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java)で入手できます。

/**
 * Runs all instrumentation found on current device.
 */
@OptionClass(alias = "installed-instrumentation")
public class InstalledInstrumentationsTest
        implements IDeviceTest, IResumableTest, IShardableTest {
    ...

    /** {@inheritDoc} */
    @Override
    public Collection<IRemoteTest> split(int shardCountHint) {
        if (shardCountHint > 1) {
            Collection<IRemoteTest> shards = new ArrayList<>(shardCountHint);
            for (int index = 0; index < shardCountHint; index++) {
                shards.add(getTestShard(shardCountHint, index));
            }
            return shards;
        }
        // Nothing to shard
        return null;
    }

    private IRemoteTest getTestShard(int shardCount, int shardIndex) {
        InstalledInstrumentationsTest shard = new InstalledInstrumentationsTest();
        try {
            OptionCopier.copyOptions(this, shard);
        } catch (ConfigurationException e) {
            CLog.e("failed to copy instrumentation options: %s", e.getMessage());
        }
        shard.mShardIndex = shardIndex;
        shard.mTotalShards = shardCount;
        return shard;
    }
    ...
}

この例では、それ自体の新しいインスタンスを作成し、それにシャードパラメーターを設定するだけです。ただし、分割ロジックはテストごとにまったく異なる場合があります。そして、それが決定論的であり、集合的に網羅的なサブセットを生成する限り、それは問題ありません。

独立

シャードは独立している必要があります!ランナーでのsplitの実装によって作成された2つのシャードは、相互に依存関係を持たないようにしたり、リソースを共有したりしないでください。

シャードの分割は決定論的である必要があります!これも必須です。同じ条件が与えられた場合、 splitメソッドは常にまったく同じシャードのリストを同じ順序で返す必要があります。

注:各シャードは異なるTFインスタンスで実行できるため、 splitロジックが相互に排他的で、決定論的な方法で集合的に網羅的なサブセットを生成するようにすることが重要です。

テストをローカルでシャーディングする方法

ローカルTFでテストをシャーディングするには、コマンドラインに--shard-countオプションを追加するだけです。

tf >run host --class com.android.tradefed.UnitTests --shard-count 3

次に、TFは各シャードのコマンドを自動的に生成して実行します。

tf >l i
Command Id  Exec Time  Device          State
3           0m:03      [null-device-2]  running stub on build 0 (shard 1 of 3)
3           0m:03      [null-device-1]  running stub on build 0 (shard 0 of 3)
3           0m:03      [null-device-3]  running stub on build 0 (shard 2 of 3)

テスト結果の集計

TFは、シャーディングされた呼び出しに対してテスト結果の集計を行わないため、レポートサービスがそれをサポートしていることを確認する必要があります。