Ao escrever um executor de teste, é importante pensar na escalabilidade. Pergunte a si mesmo: "se meu executor de teste tivesse que executar 200 mil casos de teste", quanto tempo levaria?
Sharding é uma das respostas disponíveis na Trade Federation. Requer a divisão de todos os testes que o executor precisa em vários pedaços que podem ser paralelizados.
Esta página descreve como tornar seu runner fragmentável para Tradefed.
Interface para implementar
A interface mais importante a ser implementada para ser considerada fragmentável pelo TF é IShardableTest , que contém dois métodos: split(int numShard)
e split()
.
Se o seu sharding vai depender do número de shards solicitados, você deve implementar split(int numShard)
. Caso contrário, implemente split()
.
Quando um comando de teste TF é executado com os parâmetros de sharding --shard-count
e --shard-index
, o TF itera por todos os IRemoteTest
para procurar aqueles que implementam IShardableTest
. Se encontrado, ele chamará split
para obter um novo objeto IRemoteTest
para executar um subconjunto de casos de teste para um fragmento específico.
O que devo saber sobre a implementação dividida?
- Seu runner pode fragmentar apenas sob algumas condições; nesse caso, retorne
null
quando você não fragmentou. - Tente dividir o máximo que fizer sentido: divida seu corredor em unidades de execução que façam sentido para ele. Realmente depende do seu corredor. Por exemplo: HostTest é fragmentado no nível de classe, cada classe de teste é colocada em um fragmento separado.
- Se fizer sentido, adicione algumas opções para controlar um pouco a fragmentação. Por exemplo: AndroidJUnitTest possui um
ajur-max-shard
para especificar o número máximo de fragmentos que ele pode dividir, independentemente do número solicitado.
Implementação de exemplo detalhado
Aqui está um exemplo de trecho de código implementando IShardableTest
que você pode consultar. O código completo está disponível em (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;
}
...
}
Este exemplo simplesmente cria uma nova instância de si mesmo e define parâmetros de fragmento para ela. Porém, a lógica de divisão pode ser totalmente diferente de teste para teste; e desde que seja determinístico e produza subconjuntos coletivamente exaustivos, está tudo bem.
Independência
Os fragmentos precisam ser independentes! Dois fragmentos criados pela implementação de split
em seu executor não devem ter dependências entre si nem compartilhar recursos.
A divisão de fragmentos precisa ser determinística! Isso também é obrigatório, dadas as mesmas condições, seu método split
deve sempre retornar exatamente a mesma lista de shards na mesma ordem.
NOTA: Como cada fragmento pode ser executado em diferentes instâncias do TF, é fundamental garantir que a lógica split
produza subconjuntos que sejam mutuamente exclusivos e coletivamente exaustivos de maneira determinística.
Fragmentar um teste localmente
Para fragmentar um teste em um TF local, você pode simplesmente adicionar a opção --shard-count
à linha de comando.
tf >run host --class com.android.tradefed.UnitTests --shard-count 3
Então o TF gerará comandos automaticamente para cada fragmento e os executará.
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)
Agregação de resultados de teste
Como o TF não faz nenhuma agregação de resultados de teste para invocações fragmentadas, você precisa ter certeza de que seu serviço de relatórios oferece suporte a isso.