Criar testes de unidade usando a biblioteca de testes do app Conexão Saúde

A biblioteca de testes da Conexão Saúde (androidx.health.connect:connect-testing) simplifica a criação de testes automatizados. Use essa biblioteca para verificar o comportamento do aplicativo e validar se ele responde corretamente a casos incomuns, que são difíceis de testar manualmente.

É possível usar a biblioteca para criar testes de unidade locais, que normalmente verificam o comportamento das classes no app que interagem com o cliente do Health Connect.

Para começar a usar a biblioteca, adicione-a como uma dependência de teste:

 testImplementation("androidx.health.connect:connect-testing:1.0.0-alpha01")

O ponto de entrada da biblioteca é a classe FakeHealthConnectClient, que você usa em testes para substituir o HealthConnectClient. O FakeHealthConnectClient tem os seguintes recursos:

  • Uma representação na memória de registros para que você possa inserir, remover, excluir e ler
  • Geração de tokens de mudança e acompanhamento de mudanças
  • Paginação para registros e mudanças
  • As respostas de agregação são compatíveis com stubs
  • Permite que qualquer função gere exceções
  • Um FakePermissionController que pode ser usado para emular verificações de permissões.

Para saber mais sobre como substituir dependências em testes, leia Injeção de dependência no Android. Para saber mais sobre fakes, leia Uso de testes duplos no Android.

Por exemplo, se a classe que interage com o cliente for chamada HealthConnectManager e usar um HealthConnectClient como dependência, ela será assim:

class HealthConnectManager(
    private val healthConnectClient: HealthConnectClient,
    ...
) { }

Nos testes, é possível transmitir um falso para a classe em teste:

import androidx.health.connect.client.testing.ExperimentalTestingApi
import androidx.health.connect.client.testing.FakeHealthConnectClient
import kotlinx.coroutines.test.runTest

@OptIn(ExperimentalTestingApi::class)
class HealthConnectManagerTest {

    @Test
    fun readRecords_filterByActivity() = runTest {
        // Create a Fake with 2 running records.
        val fake = FakeHealthConnectClient()
        fake.insertRecords(listOf(fakeRunRecord1, fakeBikeRecord1))

        // Create a manager that depends on the fake.
        val manager = HealthConnectManager(fake)

        // Read running records only.
        val runningRecords = manager.fetchReport(activity = Running)

        // Verify that the records were filtered correctly.
        assertTrue(runningRecords.size == 1)
    }
}

Esse teste verifica se a função fictícia fetchReport em HealthConnectManager filtra corretamente os registros por atividade.

Como verificar exceções

Quase todas as chamadas para HealthConnectClient podem gerar exceções. Por exemplo, a documentação de insertRecords menciona estas exceções:

  • @throws android.os.RemoteException para falhas de transporte de IPC.
  • @throws SecurityException para solicitações com acesso não permitido.
  • @throws java.io.IOException para problemas de E/S de disco.

Essas exceções abrangem casos como uma conexão ruim ou falta de espaço no dispositivo. Seu app precisa reagir corretamente a esses problemas de tempo de execução, já que eles podem acontecer a qualquer momento.

import androidx.health.connect.client.testing.stubs.stub

@Test
fun addRecords_throwsRemoteException_errorIsExposed() {
    // Create Fake that throws a RemoteException
    // when insertRecords is called.
    val fake = FakeHealthConnectClient()
    fake.overrides.insertRecords = stub { throw RemoteException() }

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Insert a record.
    manager.addRecords(fakeRunRecord1)

    // Verify that the manager is exposing an error.
    assertTrue(manager.errors.size == 1)
}

Agregação

As chamadas de agregação não têm implementações falsas. Em vez disso, as chamadas de agregação usam stubs que podem ser programados para se comportar de uma determinada maneira. É possível acessar os stubs pela propriedade overrides do FakeHealthConnectClient.

Por exemplo, você pode programar a função de agregação para retornar um resultado específico:

import androidx.health.connect.client.testing.AggregationResult
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import java.time.Duration

@Test
fun aggregate() {
    // Create a fake result.
    val result =
        AggregationResult(metrics =
            buildMap {
                put(HeartRateRecord.BPM_AVG, 74.0)
                put(
                    ExerciseSessionRecord.EXERCISE_DURATION_TOTAL,
                    Duration.ofMinutes(30)
                )
            }
        )

    // Create a fake that always returns the fake
    // result when aggregate() is called.
    val fake = FakeHealthConnectClient()
    fake.overrides.aggregate = stub(result)

Em seguida, verifique se a classe em teste, HealthConnectManager neste caso, processou o resultado corretamente:

// Create a manager that depends on the fake.
val manager = HealthConnectManager(fake)
// Call the function that in turn calls aggregate on the client.
val report = manager.getHeartRateReport()

// Verify that the manager is exposing an error.
assertThat(report.bpmAverage).isEqualTo(74.0)

Permissões

A biblioteca de teste inclui um FakePermissionController, que pode ser transmitido como uma dependência para FakeHealthConnectClient.

O objeto em teste pode usar a propriedade permissionController da interface HealthConnectClient (PermissionController—through) para verificar permissões. Isso geralmente é feito antes de cada chamada para o cliente.

Para testar essa funcionalidade, defina quais permissões estão disponíveis usando o FakePermissionController:

import androidx.health.connect.client.testing.FakePermissionController

@Test
fun newRecords_noPermissions_errorIsExposed() {
    // Create a permission controller with no permissions.
    val permissionController = FakePermissionController(grantAll = false)

    // Create a fake client with the permission controller.
    val fake = FakeHealthConnectClient(permissionController = permissionController)

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Call addRecords so that the permission check is made.
    manager.addRecords(fakeRunRecord1)

    // Verify that the manager is exposing an error.
    assertThat(manager.errors).hasSize(1)
}

Paginação

A paginação é uma fonte muito comum de bugs. Por isso, o FakeHealthConnectClient fornece mecanismos para ajudar você a verificar se a implementação da paginação de registros e mudanças está funcionando corretamente.

O sujeito em teste, HealthConnectManager no nosso exemplo, pode especificar o tamanho da página em ReadRecordsRequest:

fun fetchRecordsReport(pageSize: Int = 1000) }
    val pagedRequest =
        ReadRecordsRequest(
            timeRangeFilter = ...,
            recordType = ...,
            pageToken = page1.pageToken,
            pageSize = pageSize,
        )
    val page = client.readRecords(pagedRequest)
    ...

Definir o tamanho da página com um valor pequeno, como 2, permite testar a paginação. Por exemplo, é possível inserir cinco registros para que readRecords retorne três páginas diferentes:

@Test
fun readRecords_multiplePages() = runTest {

    // Create a Fake with 2 running records.
    val fake = FakeHealthConnectClient()
    fake.insertRecords(generateRunningRecords(5))

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Read records with a page size of 2.
    val report = manager.generateReport(pageSize = 2)

    // Verify that all the pages were processed correctly.
    assertTrue(report.records.size == 5)
}

Dados de teste

A biblioteca ainda não inclui APIs para gerar dados falsos, mas é possível usar os dados e geradores usados pela biblioteca na Pesquisa de código do Android.

Para simular valores de metadados em testes, use MetadataTestHelper. Isso fornece a função de extensão populatedWithTestValues(), que simula o Health Connect preenchendo valores de metadados durante a inserção de registros.

Stubs

A propriedade overrides de FakeHealthConnectClient permite programar (ou criar stubs) qualquer uma das funções para que elas gerem exceções quando forem chamadas. As chamadas de agregação também podem retornar dados arbitrários e oferecem suporte ao enfileiramento de várias respostas. Consulte Stub e MutableStub para mais informações.

Resumo dos casos extremos

  • Verifique se o app se comporta conforme o esperado quando o cliente gera exceções. Confira a documentação de cada função para descobrir quais exceções você precisa verificar.
  • Verifique se cada chamada feita ao cliente é precedida pela verificação de permissões adequada.
  • Verifique sua implementação de paginação.
  • Verifique o que acontece quando você busca várias páginas, mas uma delas tem um token expirado.