Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

샤딩된 IRemoteTest 테스트 실행기 작성

테스트 실행기를 작성할 때는 확장성을 고려하는 것이 중요합니다. 만약 테스트 실행기가 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/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 구현에 의해 생성된 두 개의 샤드는 서로에 관한 종속 항목을 지니거나 리소스를 공유해서는 안 됩니다.

샤드 분할은 확정적이어야 합니다! 이 역시 필수이지만 조건이 같다면 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는 샤딩된 호출에 관한 어떠한 테스트 결과 집계도 실행하지 않으므로 보고 서비스에서 이를 지원하는지 확인해야 합니다.