테스트 실행기를 작성할 때는 확장성을 고려하는 것이 중요합니다. 만약 테스트 실행기가 200,000건의 테스트 사례를 실행해야 했다면 얼마의 시간이 소요되었을까요?
샤딩은 Trade Federation에서 제공되는 해결책 중 하나이며, 실행기에 필요한 모든 테스트를 병렬화 가능한 여러 개의 청크로 분할하도록 요구합니다.
이 페이지에서는 Tradefed에서 실행기를 샤딩 가능하도록 만드는 방법을 설명합니다.
구현할 인터페이스
TF에서 샤딩 가능하다고 간주하도록 구현해야 하는 가장 중요한 인터페이스는 IShardableTest입니다. 여기에는 두 개의 메서드 split(int numShard)
및 split()
가 포함되어 있습니다.
샤딩이 요청된 조각 수에 따라 좌우된다면 split(int numShard)
를 구현해야 합니다. 나머지 경우에는 split()
를 구현합니다.
TF 테스트 명령어가 샤딩 매개변수인 --shard-count
및 --shard-index
로 실행되면 TF는 모든 IRemoteTest
를 반복하여 IShardableTest
를 구현하는 테스트를 찾습니다. 발견되면 split
를 호출하여 새 IRemoteTest
객체를 가져온 후 특정 샤드와 관련된 테스트 사례의 하위 집합을 실행합니다.
분할 구현에 관한 어떤 내용을 알고 있어야 할까요?
- 실행기는 일부 조건이 있는 경우에만 샤딩할 수 있습니다. 이 경우에는 샤딩하지 않았을 때
null
을 반환합니다. - 합리적인 선에서 최대한 많이 분할을 시도해야 합니다. 합리적인 선에서 실행기를 실행 단위로 분할하세요. 결국에는 실행기에 달렸다는 의미입니다. 예를 들어 HostTest는 클래스 수준에서 샤딩되며, 각 테스트 클래스는 별도의 샤드에 배치됩니다.
- 합리적이라고 생각되면 옵션을 추가하여 샤딩을 조금만 제어합니다.
예를 들어 요청한 개수와 상관없이 AndroidJUnitTest에는 분할 가능한 최대 샤드 수를 지정하기 위한
ajur-max-shard
가 있습니다.
구현의 상세한 예
다음은 참조 가능한 IShardableTest
를 구현하는 코드 스니펫의 예입니다. 전체 코드는 다음 페이지에서 확인할 수 있습니다. https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/main/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
구현에 의해 생성된 두 개의 샤드는 서로에 관한 종속 항목을 지니거나 리소스를 공유해서는 안 됩니다.
샤드 분할은 확정적이어야 합니다! 이 역시 필수이지만 조건이 같다면 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는 샤딩된 호출에 관한 어떠한 테스트 결과 집계도 실행하지 않으므로 보고 서비스에서 이를 지원하는지 확인해야 합니다.