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 zajmie uruchomienie 200 tys. przypadków testowych przez moje narzędzie do uruchamiania testów?”.

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

Z tej strony dowiesz się, jak sprawić, aby Twoje narzędzie do uruchamiania testów było podzielne na fragmenty w Tradefed.

Interfejs do zaimplementowania

Najważniejszym interfejsem, który należy zaimplementować, aby TF uznał narzędzie do uruchamiania testów za podzielne na fragmenty, jest TF to IShardableTest, który zawiera 2 metody: split(int numShard) i split().

Jeśli dzielenie na fragmenty ma zależeć od liczby żądanych fragmentów, zaimplementuj metodę split(int numShard). W przeciwnym razie zaimplementuj metodę split().

Gdy polecenie testowe TF jest wykonywane z parametrami dzielenia na fragmenty --shard-count i --shard-index, TF iteruje po wszystkich IRemoteTest, aby znaleźć te, które implementują IShardableTest. Jeśli je znajdzie, wywoła metodę split, aby uzyskać nowy obiekt IRemoteTest, który uruchomi podzbiór przypadków testowych dla określonego fragmentu.

Co muszę wiedzieć o implementacji podziału?

  • Narzędzie do uruchamiania testów może dzielić na fragmenty tylko pod pewnymi warunkami. W takim przypadku, jeśli nie podzielisz na fragmenty, zwróć wartość null.
  • Staraj się dzielić na fragmenty tak bardzo, jak to ma sens. Podziel narzędzie do uruchamiania testów na jednostki wykonawcze, które mają dla niego sens. To zależy od narzędzia do uruchamiania testów. Na przykład: HostTest jest dzielony na fragmenty na poziomie klasy. Każda klasa testowa jest umieszczana w osobnym fragmencie.
  • Jeśli ma to sens, dodaj kilka opcji, które pozwolą nieco kontrolować dzielenie na fragmenty. 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/android17-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 dzielenia może się jednak różnić w zależności od testu. Jeśli jest deterministyczna i daje łącznie wyczerpujące podzbiory, jest w porządku.

Niezależność

Fragmenty muszą być niezależne. Dwa fragmenty utworzone przez implementację metody split w narzędziu do uruchamiania testów nie powinny mieć od siebie zależności ani współdzielić zasobów.

Dzielenie na fragmenty musi być deterministyczne. 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 być uruchamiany na różnych instancjach TF, ważne jest, aby logika split dawała podzbiory, które są wzajemnie wykluczające się i łącznie wyczerpujące w sposób deterministyczny.

Dzielenie testu na fragmenty lokalnie

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

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

TF automatycznie utworzy 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 testu

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