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, który należy wdrożyć, 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 do uruchomienia podzbioru 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óć wartość null, jeśli nie podzieliła 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-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, ponieważ 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.