Napisz podzielonego na fragmenty testera IRemoteTest

Pisząc program uruchamiający testy, ważne jest, aby pomyśleć o skalowalności. Zadaj sobie pytanie: „gdyby mój tester musiał przeprowadzić 200 000 przypadków testowych”, ile czasu by to zajęło?

Sharding to jedna z odpowiedzi dostępnych w Federacji Handlowej. Wymaga podzielenia wszystkich testów potrzebnych biegaczowi na kilka części, które można zrównoleglić.

Na tej stronie opisano, jak udostępnić swojego biegacza do wymiany na Tradefed.

Interfejs do wdrożenia

Najważniejszym interfejsem do zaimplementowania, który ma zostać uznany przez TF za możliwy do fragmentowania, jest IShardableTest , który zawiera dwie metody: split(int numShard) i split() .

Jeśli fragmentowanie będzie zależeć od liczby żądanych fragmentów, powinieneś zaimplementować split(int numShard) . W przeciwnym razie zaimplementuj split() .

Gdy wykonywane jest polecenie testowe TF z parametrami shardingu --shard-count i --shard-index , TF iteruje po wszystkich IRemoteTest w poszukiwaniu tych, które implementują IShardableTest . Jeśli zostanie znaleziony, wywoła split , aby uzyskać nowy obiekt IRemoteTest w celu uruchomienia podzbioru przypadków testowych dla określonego fragmentu.

Co powinienem wiedzieć o implementacji podzielonej?

  • Twój biegacz może odłamać się tylko pod pewnymi warunkami; w takim przypadku zwróć wartość null , jeśli nie dokonałeś fragmentacji.
  • Spróbuj podzielić tyle, ile ma to sens: podziel biegacza na jednostki wykonania, które mają dla niego sens. To naprawdę zależy od biegacza. Na przykład: HostTest jest podzielony na poziomie klasy, każda klasa testowa jest umieszczana w osobnym fragmencie.
  • Jeśli ma to sens, dodaj kilka opcji, aby trochę kontrolować fragmentowanie. Na przykład: AndroidJUnitTest ma ajur-max-shard , który określa maksymalną liczbę fragmentów, na które może zostać podzielony, niezależnie od żądanej liczby.

Szczegółowa przykładowa realizacja

Oto przykładowy fragment kodu implementujący IShardableTest do którego możesz się odwołać. Pełny kod jest dostępny pod adresem (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;
    }
    ...
}

Ten przykład po prostu tworzy nową instancję samego siebie i ustawia dla niej parametry fragmentu. Jednakże logika podziału może być całkowicie różna w zależności od testu; i dopóki jest deterministyczny i daje łącznie wyczerpujące podzbiory, jest w porządku.

Niezależność

Odłamki muszą być niezależne! Dwa fragmenty utworzone w wyniku implementacji split w programie runner nie powinny być od siebie zależne ani dzielić zasobów.

Podział fragmentów musi być deterministyczny! Jest to również obowiązkowe, biorąc pod uwagę te same warunki, Twoja metoda split powinna zawsze zwracać dokładnie tę samą listę fragmentów w tej samej kolejności.

UWAGA: Ponieważ każdy fragment może działać na różnych instancjach TF, niezwykle ważne jest zapewnienie, że logika split daje podzbiory, które wzajemnie się wykluczają i są łącznie wyczerpujące w sposób deterministyczny.

Wykonaj test lokalnie

Aby podzielić test na lokalnym TF, możesz po prostu dodać opcję --shard-count do wiersza poleceń.

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

Następnie TF automatycznie wyświetli polecenia dla każdego fragmentu i uruchomi je.

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)

Agregacja wyników testu

Ponieważ TF nie wykonuje żadnej agregacji wyników testów dla wywołań podzielonych na fragmenty, musisz upewnić się, że Twoja usługa raportowania to obsługuje.