Ao escrever um executor de testes, é importante pensar na capacidade de escalonamento. Perguntar "Se meu executor tivesse que executar 200 mil casos de teste" quanto tempo isso levaria?
A fragmentação é uma das respostas disponíveis na Trade Federation. É necessário dividir todos os testes necessários para o executor em vários blocos que podem ser executados em paralelo.
Esta página descreve como tornar o runner fragmentável para Tradefed.
Interface a ser implementada
A interface mais importante a ser implementada para ser considerada particionável pelo
TF é
IShardableTest,
que contém dois métodos: split(int numShard)
e split()
.
Se a fragmentação depender do número de fragmentos solicitados,
precisa implementar split(int numShard)
. Caso contrário, implemente split()
.
Quando um comando de teste do TF é executado com parâmetros de fragmentação --shard-count
e
--shard-index
, o TF itera por todos os IRemoteTest
para procurar aqueles
que implementam IShardableTest
. Se encontrado, ele vai chamar split
para
receber um novo objeto IRemoteTest
para executar um subconjunto de casos de teste para um determinado
no fragmento.
O que preciso saber sobre a implementação de divisão?
- O corredor pode fazer a fragmentação apenas sob algumas condições; Nesse caso, retorne
null
. sem fragmentação. - Tente dividir o que quiser: divida o executor em uma unidade de de uma execução que faça sentido para ele. Depende do seu corredor. Por exemplo: HostTest é dividido no nível da classe, cada classe de teste é colocada em um fragmento separado.
- Se fizer sentido, adicione algumas opções para controlar o fragmentação.
Por exemplo:
AndroidJUnitTest (link em inglês)
tem um
ajur-max-shard
para especificar o número máximo de fragmentos que pode dividido, independentemente do número solicitado.
Exemplo detalhado de implementação
Confira um exemplo de snippet de código que implementa IShardableTest
que você pode
usar como referência. 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 apenas cria uma nova instância de si mesma e define o fragmento parâmetros a ela. No entanto, a lógica de divisão pode ser totalmente diferente teste para testar; e contanto que seja determinista e produza coletivamente subconjuntos exaustivos, tudo bem.
Independência
Os fragmentos precisam ser independentes. Dois fragmentos criados por sua implementação do
split
no executor não pode ter dependências uns com os outros nem compartilhar
do Google Cloud.
A divisão de fragmentos precisa ser determinística. Isso também é obrigatório, considerando as
mesmas condições, seu método split
sempre retornará a mesma lista de
fragmentos na mesma ordem.
OBSERVAÇÃO: como cada fragmento pode ser executado em diferentes instâncias do TF, é fundamental
garantir que a lógica split
gere subconjuntos mutuamente exclusivos e
coletivamente exaustivos de maneira determinística.
Dividir um teste localmente
Para fragmentar um teste em um TF local, basta adicionar a opção --shard-count
ao
na linha de comando.
tf >run host --class com.android.tradefed.UnitTests --shard-count 3
Em seguida, o TF vai gerar comandos automaticamente para cada fragmento e executá-los.
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 testes
Como o TF não faz nenhuma agregação de resultados de teste para invocações divididas, é necessário verificar se o serviço de relatórios oferece suporte a isso.