テストランナーを作成するときは、スケーラビリティについて考えることが重要です。 「私のテストランナーが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は、シャーディングされた呼び出しに対してテスト結果の集計を行わないため、レポートサービスがそれをサポートしていることを確認する必要があります。