Tworzenie dzielonego modułu testowego IRemoteTest

Podczas pisania narzędzia do uruchamiania testów należy pamiętać o skalowalności. Zadaj sobie pytanie: „Ile czasu zajęłoby uruchomienie 200 tys. przypadków testowych przez narzędzie do uruchamiania testów?”.

Dzielenie na partycje to jedna z odpowiedzi dostępnych w Trade Federation. Wymaga to podzielenia wszystkich testów, które ma wykonać narzędzie, na kilka części, które można uruchamiać równolegle.

Z tego artykułu dowiesz się, jak sprawić, aby moduł uruchamiający był dzielony na fragmenty w Tradefed.

Interfejs do wdrożenia

Najważniejszym interfejsem do wdrożenia, aby TF uznał test za możliwy do podzielenia na fragmenty, jest IShardableTest, który zawiera 2 metody: split(int numShard)split().

Jeśli podział na fragmenty ma zależeć od liczby żądanych fragmentów, musisz wdrożyć split(int numShard). W przeciwnym razie wdróż split().

Gdy polecenie testowe TF jest wykonywane z parametrami podziału --shard-count--shard-index, TF iteruje wszystkie IRemoteTest, aby znaleźć te, które implementują IShardableTest. Jeśli zostanie znaleziony, wywoła funkcję split, aby uzyskać nowy obiekt IRemoteTest i uruchomić podzbiór przypadków testowych dla określonego fragmentu.

Co muszę wiedzieć o wdrażaniu podzielonym?

  • Aplikacja uruchamiająca może dzielić dane tylko pod pewnymi warunkami. W takim przypadku zwróć null, jeśli nie podzielisz danych.
  • Staraj się dzielić zadania tak, aby miało to sens: podziel runnera na jednostki wykonawcze, które mają dla niego sens. To zależy od biegacza. Na przykład: HostTest jest dzielony na poziomie klasy, a każda klasa testowa jest umieszczana w osobnym fragmencie.
  • Jeśli to ma sens, dodaj kilka opcji, aby trochę kontrolować dzielenie. Na przykład:AndroidJUnitTest ma parametr ajur-max-shard, który określa maksymalną liczbę fragmentów, na które można podzielić test, niezależnie od żądanej liczby.

Szczegółowy przykład implementacji

Oto przykładowy fragment kodu implementujący IShardableTest, z którego możesz skorzystać. Pełny kod jest dostępny pod adresem (https://android.googlesource.com/platform/tools/tradefederation/+/refs/heads/android16-qpr1-release/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. Logika podziału może się jednak różnić w zależności od testu. Ważne jest, aby była deterministyczna i tworzyła łącznie wyczerpujące podzbiory.

Independence

Fragmenty muszą być niezależne. Dwa fragmenty utworzone przez implementację split w programie uruchamiającym nie powinny być od siebie zależne ani współdzielić zasobów.

Podział fragmentów musi być deterministyczny. Jest to również obowiązkowe. W tych samych warunkach 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, ważne jest, aby logika split generowała podzbiory, które są wzajemnie wykluczające się i łącznie wyczerpujące w deterministyczny sposób.

Dzielenie testu lokalnie

Aby podzielić test na partycje na lokalnym TF, wystarczy dodać opcję --shard-count do wiersza poleceń.

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

Następnie TF automatycznie wygeneruje polecenia dla każdego fragmentu i je uruchomi.

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 testów

TF nie agreguje wyników testów w przypadku wywołań podzielonych na fragmenty, więc musisz się upewnić, że Twoja usługa raportowania to obsługuje.