Este tutorial faz parte do curso Android avançado em Kotlin. Você obterá o máximo valor deste curso se trabalhar com os tutoriais em sequência, mas isso não é obrigatório. Todos os tutoriais do curso estão listados na página de destino Android avançado em Kotlin.

Introdução

Quando você implementou o primeiro recurso de seu primeiro aplicativo, provavelmente executou o código para verificar se funcionava conforme o esperado. Você realizou um teste, embora seja um teste manual. À medida que continuou a adicionar e atualizar recursos, provavelmente também continuou a executar seu código e a verificar se ele funciona. Mas fazer isso manualmente todas as vezes é cansativo, sujeito a erros e não tem escala.

Os computadores são ótimos em dimensionamento e automação! Portanto, os desenvolvedores em grandes e pequenas empresas escrevem automated tests, que são testes executados por software e não exigem que você opere manualmente o aplicativo para verificar se o código funciona.

O que você aprenderá nesta série de tutoriais é como criar uma coleção de testes (conhecida como conjunto de teste) para um aplicativo do mundo real.

Este primeiro tutorial cobre os conceitos básicos de teste no Android, você escreverá seus primeiros testes e aprenderá como testar LiveData e ViewModel s.

O que você já deveria saber

Você deve estar familiarizado com:

O que você aprenderá

Você aprenderá sobre os seguintes tópicos:

Você aprenderá sobre as seguintes bibliotecas e conceitos de código:

O que você vai fazer

Nesta série de tutoriais, você trabalhará com o aplicativo TO-DO Notes. O aplicativo permite que você anote as tarefas a serem concluídas e as exiba em uma lista. Você pode então marcá-los como concluídos ou não, filtrá-los ou excluí-los.

Este aplicativo foi escrito em Kotlin, tem várias telas, usa componentes Jetpack e segue a arquitetura de um Guia para arquitetura de aplicativo. Ao aprender como testar este aplicativo, você poderá testar aplicativos que usam as mesmas bibliotecas e arquitetura.

Para começar, baixe o código:

Baixe o Zip

Como alternativa, você pode clonar o repositório Github para o código:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

Você pode navegar pelo código no android-testing Github repository.

Nesta tarefa, você executará o aplicativo e explorará a base de código.

Etapa 1: execute o aplicativo de amostra

Depois de baixar o aplicativo TO-DO, abra-o no Android Studio e execute-o. Deve compilar. Explore o aplicativo fazendo o seguinte:

Etapa 2: explore o código do aplicativo de amostra

O aplicativo TO-DO é baseado no popular exemplo de teste e arquitetura de Projetos de arquitetura (usando a versão de arquitetura reativa do exemplo). O aplicativo segue a arquitetura de um Guia para arquitetura de aplicativo. Ele usa ViewModels com Fragments, um repositório e Room. Se você estiver familiarizado com algum dos exemplos abaixo, este aplicativo tem uma arquitetura semelhante:

É mais importante que você entenda a arquitetura geral do aplicativo do que um entendimento profundo da lógica de qualquer camada.

Aqui está o resumo dos pacotes que você encontrará:

Package: com.example.android.architecture.blueprints.todoapp

.addedittask

The add or edit a task screen: código da camada de IU para adicionar ou editar uma tarefa.

.data

A camada de dados: lida com a camada de dados das tarefas. Ele contém o banco de dados, a rede e o código do repositório.

.statistics

A tela de estatística: Código da camada de IU para a tela de estatísticas.

.taskdetail

A tela de detalhe de tarefa: código da camada de IU para uma única tarefa.

.tasks

A tela de tarefas: código da camada de IU para a lista de todas as tarefas.

.util

Classes utilitárias: classes compartilhadas usadas em várias partes do aplicativo, por exemplo, para o layout de atualização de furto usado em várias telas.

Camada de dados (.data)

Este aplicativo inclui uma camada de rede simulada, no pacote remote, e uma camada de banco de dados, no pacote local. Para simplificar, neste projeto a camada de rede é simulada com apenas um HashMap com um atraso, em vez de fazer solicitações de rede reais.

O DefaultTasksRepository coordena ou faz a mediação entre a camada de rede e a camada de banco de dados e é o que retorna dados para a camada de IU.

Camada de IU (.addedittask, .statistics, .taskdetail, .tasks)

Cada um dos pacotes da camada de IU contém um fragmento e um modelo de vista, junto com quaisquer outras classes que são necessárias para a IU (como um adaptador para a lista de tarefas). A TaskActivity é a atividade que contém todos os fragmentos.

Navegação

A navegação para o aplicativo é controlada pelo componente de navegação. Ele é definido no arquivo nav_graph.xml. A navegação é acionada nos modelos de vista usando a classe Event; os modelos de vista também determinam quais argumentos passar. Os fragmentos observam os Event se fazem a navegação real entre as telas.

Nesta tarefa, você executará seus primeiros testes.

  1. No Android Studio, abra o painel Project e encontre estas três pastas:

Essas pastas são conhecidas como source sets. Os conjuntos de fontes são pastas que contêm o código-fonte do seu aplicativo. Os conjuntos de origem, que são coloridos em verde (androidTest e test), contêm seus testes. Ao criar um novo projeto Android, você obtém os três conjuntos de fontes a seguir por padrão. Eles são:

A diferença entre testes locais e testes instrumentados está na maneira como são executados.

Testes locais (teste conjunto de origem)???

Esses testes são executados localmente na JVM de sua máquina de desenvolvimento e não requerem um emulador ou dispositivo físico. Por causa disso, eles correm rápido, mas sua fidelidade é menor, o que significa que eles agem menos como fariam no mundo real.

No Android Studio, os testes locais são representados por um ícone de triângulo verde e vermelho.

Testes instrumentados (androidTest conjunto de origem)???

Esses testes são executados em dispositivos Android reais ou emulados, portanto, eles refletem o que acontecerá no mundo real, mas também são muito mais lentos.

No Android Studio, os testes instrumentados são representados por um Android com um ícone de triângulo verde e vermelho.

Etapa 1: execute um teste local

  1. Abra a pasta test até encontrar o arquivo ExampleUnitTest.kt.
  2. Clique com o botão direito do mouse e selecione Run ExampleUnitTest.

Você deve ver a seguinte saída na janela Run na parte inferior da tela:

  1. Observe as marcas de verificação verdes e expanda os resultados do teste para confirmar que um teste chamado addition_isCorrect passou. É bom saber que a adição funciona conforme o esperado!

Etapa 2: faça o teste falhar

Abaixo está o teste que você acabou de executar.

ExampleUnitTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

Observe que os testes

O Android usa a biblioteca de teste JUnit para teste (neste tutorial JUnit4). Ambas, as asserções e a anotação @Test vêm do JUnit.

Uma asserção é o núcleo do seu teste. É uma instrução de código que verifica se seu código ou aplicativo se comportou conforme o esperado. Nesse caso, a asserção é assertEquals(4, 2 + 2) que verifica se 4 é igual a 2 + 2.

Para ver a aparência de um teste com falha, adicione uma afirmação que você pode facilmente ver que deve falhar. Ele verificará se 3 é igual a 1 + 1.

  1. Adicione assertEquals(3, 1 + 1) ao teste addition_isCorrect.

ExampleUnitTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. Execute o teste.
  1. Nos resultados do teste, observe um X próximo ao teste.

  1. Observe também:

Etapa 3: execute um teste instrumentado

Os testes instrumentados estão no conjunto de origem androidTest.

  1. Abra o conjunto de origem androidTest.
  2. Execute o teste chamado ExampleInstrumentedTest.

ExampleInstrumentedTest

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

Ao contrário do teste local, este teste é executado em um dispositivo (no exemplo abaixo, um smartphone Pixel 2 emulado):

Se você tiver um dispositivo conectado ou um emulador em execução, deverá ver o teste executado no emulador.

Nesta tarefa, você escreverá testes para getActiveAndCompleteStats, que calcula a porcentagem de estatísticas de tarefas ativas e completas para seu aplicativo. Você pode ver esses números na tela de estatísticas do aplicativo.

Etapa 1: criar uma classe de teste

  1. No conjunto de origem main, em todoapp.statistics, abra StatisticsUtils.kt.
  2. Encontre a função getActiveAndCompletedStats.

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

A função getActiveAndCompletedStats aceita uma lista de tarefas e retorna um StatsResult. StatsResult é uma classe de dados que contém dois números, a porcentagem de tarefas que estão concluídas e a porcentagem que estão ativas.

O Android Studio oferece ferramentas para gerar esboços de teste e ajudá-lo a implementar os testes dessa função.

  1. Clique com o botão direito em getActiveAndCompletedStats e selecione Generate> Test.

A caixa de diálogo Create Test é aberta:

  1. Altere o Class name: para StatisticsUtilsTest (em vez de StatisticsUtilsKtTest; é um pouco melhor não ter KT no nome da classe de teste).
  2. Mantenha o resto como padrão. JUnit 4 é a biblioteca de teste apropriada. O pacote de destino está correto (ele reflete a localização da classe StatisticsUtils) e você não precisa marcar nenhuma das caixas de seleção (isso apenas gera código extra, mas você escreverá seu teste a partir de coçar, arranhão).
  3. Pressione OK

A caixa de diálogo Choose Destination Directory é aberta:

Você fará um teste local porque sua função está fazendo cálculos matemáticos e não incluirá nenhum código específico do Android. Portanto, não há necessidade de executá-lo em um dispositivo real ou emulado.

  1. Selecione o diretório test (não androidTest) porque você escreverá testes locais.
  2. Clique em OK.
  3. Observe a classe gerada StatisticsUtilsTest em test/statistics/.

Etapa 2: Escreva sua primeira função de teste

Você vai escrever um teste que verifica:

  1. Abra StatisticsUtilsTest.
  2. Crie uma função chamada getActiveAndCompletedStats_noCompleted_returnsHundredZero.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. Adicione a anotação @Test acima do nome da função para indicar que é um teste.
  2. Crie uma lista de tarefas.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. Chame getActiveAndCompletedStats com essas tarefas.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. Verifique se result é o que você esperava, usando asserções.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

Aqui está o código completo.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. Execute o teste (clique com o botão direito em StatisticsUtilsTest e selecione Run).

Deve passar:

Etapa 3: adicione a dependência de Hamcrest

Como seus testes atuam como documentação do que seu código faz, é bom quando eles podem ser lidos por humanos. Compare as duas afirmações a seguir:

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

A segunda afirmação parece muito mais com uma frase humana. Ele é escrito usando uma estrutura de asserção chamada Hamcrest. Outra boa ferramenta para escrever asserções legíveis é a Biblioteca Truth. Você usará o Hamcrest neste tutorial para escrever asserções.

  1. Abra build.grade (Module: app) e adicione a seguinte dependência.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

Normalmente, você usa implementation ao adicionar uma dependência, mas aqui você está usando testImplementation. Quando você estiver pronto para compartilhar seu aplicativo com o mundo, é melhor não aumentar o tamanho do seu APK com nenhum código de teste ou dependências em seu aplicativo. Você pode designar se uma biblioteca deve ser incluída no código principal ou de teste usando configurações do gradle. As configurações mais comuns são:

A configuração que você usa define onde a dependência pode ser usada. Se você escrever:

testImplementation "org.hamcrest: hamcrest-all: $ hamcrestVersion"

Isso significa que o Hamcrest estará disponível apenas no conjunto de fonte de teste. Também garante que o Hamcrest não será incluído em seu aplicativo final.

Etapa 4: use o Hamcrest para escrever asserções

  1. Atualize o teste getActiveAndCompletedStats_noCompleted_returnsHundredZero() para usar o assertThat de Hamcrest em vez de assertEquals.
// TROQUE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// COM
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

Observe que você pode usar import import org.hamcrest.Matchers.`is` se solicitado.

O teste final será semelhante ao código abaixo.

StatisticsUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

    }
}
  1. Execute seu teste atualizado para confirmar se ainda funciona!

Este tutorial não lhe ensinará todos os prós e contras de Hamcrest, então se você quiser aprender mais confira o tutorial oficial.

Esta é uma tarefa opcional para prática.

Nesta tarefa, você escreverá mais testes usando JUnit e Hamcrest. Você também escreverá testes usando uma estratégia derivada da prática do programa de Desenvolvimento Guiado por Testes. O Desenvolvimento Guiado por Testes ou DGT é uma escola de pensamento de programação que diz que em vez de escrever seu código de recurso primeiro, você escreve seus testes primeiro. Em seguida, você escreve seu código de recurso com o objetivo de passar nos testes.

Etapa 1. Escreva os testes

Escreva testes para quando você tiver uma lista de tarefas normal:

  1. Se houver uma tarefa concluída e nenhuma tarefa ativa, a porcentagem de activeTasks deve ser 0f, e a porcentagem de tarefas concluídas deve ser 100f.
  2. Se houver duas tarefas concluídas e três tarefas ativas, a porcentagem concluída deve ser 40f e a porcentagem ativa deve ser 60f.

Etapa 2. Escreva um teste para um erro

O código para getActiveAndCompletedStats conforme escrito tem um erro. Observe como ele não trata corretamente com o que acontece se a lista estiver vazia ou nula. Em ambos os casos, ambas as porcentagens devem ser zero.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

Para corrigir o código e escrever testes, você usará o desenvolvimento guiado a testes. O Desenvolvimento Guiado a Testes segue estas etapas.

  1. Escreva o teste, usando a estrutura Dado, Quando, Então e com um nome que siga a convenção.
  2. Confirme se o teste falhou.
  3. Escreva o código mínimo para que o teste seja aprovado.
  4. Repita para todos os testes!

Em vez de começar corrigindo o erro, você começará escrevendo os testes primeiro. Em seguida, você pode confirmar que tem testes que o protegem de reintroduzir acidentalmente esses erros no futuro.

  1. Se houver uma lista vazia (emptyList()), então ambas as porcentagens devem ser 0f.
  2. Se ocorrer um erro ao carregar as tarefas, a lista será null, e ambas as porcentagens devem ser 0f.
  3. Execute seus testes e confirme se eles falham:

Etapa 3. Corrigir o erro

Agora que você tem seus testes, corrija o erro.

  1. Corrija o erro em getActiveAndCompletedStats retornando 0f se tasks for null ou vazio:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. Execute seus testes novamente e verifique se todos os testes foram aprovados!

Seguindo o DGT e escrevendo os testes primeiro, você ajudou a garantir que:

Solução: escrever mais testes

Aqui estão todos os testes e o código de recurso correspondente.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

Excelente trabalho com o básico de escrever e executar testes! A seguir, você aprenderá a escrever testes ViewModel e LiveData básicos.

No restante do tutorial, você aprenderá como escrever testes para duas classes Android que são comuns na maioria dos aplicativos—ViewModel e LiveData.

Você começa escrevendo testes para o TasksViewModel.


Você vai se concentrar em testes que têm toda a sua lógica no modelo de vista e não dependem do código do repositório. O código do repositório envolve código assíncrono, bancos de dados e chamadas de rede, que adicionam complexidade ao teste. Você vai evitar isso por enquanto e se concentrar em escrever testes para a funcionalidade ViewModel que não testa nada diretamente no repositório.



O teste que você escreverá irá verificar se quando você chama o método addNewTask, o Event para abrir a nova janela de tarefa é disparado. Aqui está o código do aplicativo que você testará.

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

Etapa 1. Faça uma classe TasksViewModelTest

Seguindo as mesmas etapas que você fez para StatisticsUtilTest, nesta etapa, você cria um arquivo de teste para TasksViewModelTest.

  1. Abra a classe que deseja testar, no pacote tasks, TasksViewModel.
  2. No código, clique com o botão direito sobre o nome da classe TasksViewModel -> Generate -> Test.

  1. Na tela Create Test, clique em OK para aceitar (não há necessidade de alterar nenhuma das configurações padrão).
  2. Na caixa de diálogo Choose Destination Directory, escolha o diretório test.

Etapa 2. Comece a escrever seu teste ViewModel

Nesta etapa, você adiciona um teste de modelo de vista para testar se, ao chamar o método addNewTask, o Event para abrir a nova janela de tarefa é disparado.

  1. Crie um novo teste chamado addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

E quanto ao contexto do aplicativo?

Quando você cria uma instância de TasksViewModel para testar, seu construtor requer um Contexto de aplicativo. Mas, neste teste, você não está criando um aplicativo completo com atividades, IU e fragmentos, então como obter um contexto de aplicativo?

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

As Bibliotecas de teste do AndroidX incluem classes e métodos que fornecem versões de componentes como aplicativos e atividades destinados a testes. Quando você tem um teste local em que precisa de classes simuladas do Android framework (como um contexto de aplicativo), siga estas etapas para configurar o teste AndroidX de maneira adequada:

  1. Adicione o AndroidX Test núcleo e dependências ext
  2. Adicione a dependência da biblioteca de testes Robolectric
  3. Anote a classe com o executor de teste AndroidJunit4
  4. Escreva o código de teste AndroidX

Você vai concluir essas etapas e then entender o que eles fazem juntos.

Etapa 3. Adicionar as dependências do Gradle

  1. Copie essas dependências para o arquivo build.gradle do módulo do aplicativo para adicionar o núcleo do AndroidX Test e as dependências ext, bem como a dependência de teste Robolectric.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

Etapa 4. Adicionar executor de teste JUnit

  1. Adicione @RunWith(AndroidJUnit4::class) acima de sua classe de teste.

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

Etapa 5. Use o teste AndroidX

Neste ponto, você pode usar a biblioteca de teste??? AndroidX. Isso inclui o método ApplicationProvider.getApplicationContext, que obtém um Contexto de aplicativo.

  1. Crie um TasksViewModel usando ApplicationProvider.getApplicationContext() da biblioteca de teste AndroidX.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. Chame addNewTask em tasksViewModel.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

Neste ponto, seu teste deve ser semelhante ao código abaixo.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. Execute seu teste para confirmar se ele funciona.

Conceito: como funciona o AndroidX Test?

O que é o AndroidX Test?

AndroidX Test é uma coleção de bibliotecas para teste. Inclui classes e métodos que fornecem versões de componentes como Aplicativos e Atividades, destinados a testes. Como exemplo, este código que você escreveu é um exemplo de função de teste AndroidX para obter um contexto de aplicativo.

ApplicationProvider.getApplicationContext()

Um dos benefícios das APIs de teste do AndroidX é que elas são criadas para funcionar tanto para testes locais and instrumentados. Isso é bom porque:

Por exemplo, como você escreveu seu código usando bibliotecas de teste AndroidX, você pode mover sua classe TasksViewModelTest da pasta test para a pasta androidTest e a os testes ainda serão executados. O getApplicationContext() funciona de maneira um pouco diferente, dependendo se está sendo executado como um teste local ou instrumentado:

O que é Robolectric?

O ambiente Android simulado que o AndroidX Test usa para testes locais é fornecido por Robolectric. Robolectric é uma biblioteca que cria um ambiente Android simulado para testes e roda mais rápido do que inicializar um emulador ou rodar em um dispositivo. Sem a dependência Robolectric, você obterá este erro:

O que@RunWith(AndroidJUnit4::class) faz?

Um executador de testeé um componente JUnit que executa testes. Sem um executor de teste, seus testes não seriam executados. Existe um executor de teste padrão fornecido pelo JUnit que você obtém automaticamente. @RunWith troca esse executor de teste padrão.

O executor de teste AndroidJUnit4 permite que o AndroidX Test execute seu teste de forma diferente, dependendo se eles são instrumentados ou testes locais.

Etapa 6. Corrigir avisos do Robolectric

Ao executar o código, observe que o Robolectric é usado.

Por causa do AndroidX Test e do AndroidJunit4 test runner???, isso é feito sem que você escreva diretamente uma única linha de código Robolectric!

Você pode notar dois avisos.

Você pode corrigir o aviso No such manifest file: ./AndroidManifest.xml, atualizando seu arquivo gradle.

  1. Adicione a seguinte linha ao seu arquivo gradle para que o manifesto Android correto seja usado. A opção includeAndroidResources permite que você acesse recursos do Android em seus testes unitários, incluindo seu arquivo AndroidManifest.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

O aviso "WARN: Android SDK 29 requires Java 9..." é mais complicado. A execução de testes no Android Q requer Java 9. Em vez de tentar configurar o Android Studio para usar Java 9, para este tutorial, mantenha seu destino e compile o SDK em 28.

Em resumo:

Parabéns, você está usando a biblioteca de teste do AndroidX e o Robolectric para executar um teste. Seu teste não terminou (você ainda não escreveu uma declaração assert, ela apenas diz // TODO test LiveData). Você aprenderá a escrever declarações assert com LiveData a seguir.

Nesta tarefa, você aprenderá como declarar corretamente o valor LiveData.

Foi aqui que você parou sem o teste do modelo de vista addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

Para testar o LiveData, é recomendável fazer duas coisas:

  1. Use InstantTaskExecutorRule
  2. Garanta a observação do LiveData

Etapa 1. Use InstantTaskExecutorRule

InstantTaskExecutorRule é uma JUnit Rule???. Quando você o usa com a anotação @get:Rule, faz com que algum código na classe InstantTaskExecutorRule seja executado antes e depois dos testes (para ver o código exato, você pode usar o atalho de teclado Command+B para ver o arquivo).

Esta regra executa todos os trabalhos de segundo plano relacionados aos componentes de arquitetura na mesma thread para que os resultados do teste ocorram de forma síncrona e em uma ordem repetível. Ao escrever testes que incluem teste LiveData, use esta regra!

  1. Adicione a dependência do gradle para a biblioteca de teste principal dos Componentes de arquitetura (que contém esta regra).

app/build.gradle

testImplementation "androidx.arch.core: core-testing: $ archTestingVersion"
  1. Abra TasksViewModelTest.kt
  2. Adicione o InstantTaskExecutorRule dentro da classe TasksViewModelTest.

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

Etapa 2. Adicionar a classe LiveDataTestUtil.kt

Sua próxima etapa é garantir que o LiveData que você está testando seja observado.

Quando você usa LiveData, normalmente tem uma atividade ou fragmento (LifecycleOwner) observe o LiveData.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

Essa observação é importante. Você precisa de observadores ativos no LiveData para

Para obter o comportamento esperado do LiveData para o LiveData do seu modelo de vista, você precisa observar o LiveData com um LifecycleOwner.

Isso representa um problema: em seu teste TasksViewModel, você não tem uma atividade ou fragmento para observar seu LiveData. Para contornar isso, você pode usar o método observeForever, que garante que o LiveData seja constantemente observado, sem a necessidade de um LifecycleOwner. Quando você observeForever, você precisa se lembrar de remover seu observador ou arriscar um vazamento do observador.

Isso se parece com o código abaixo. Examine:

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

Isso é muito código clichê??? para observar um único LiveData em um teste! Existem algumas maneiras de se livrar desse boilerplate. Você vai criar uma função de extensão chamada LiveDataTestUtil para tornar a adição de observadores mais simples.

  1. Faça um novo arquivo Kotlin chamado LiveDataTestUtil.kt em seu conjunto de origem test.


  1. Copie e cole o código abaixo.

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

Este é um método bastante complicado. Ele cria uma função de extensão Kotlin chamada getOrAwaitValue que adiciona um observador, obtém o valor de LiveData e, em seguida, limpa o observador - basicamente um curto, versão reutilizável do código observeForever mostrado acima. Para obter uma explicação completa desta classe, verifique esta postagem do blog.

Etapa 3. Use getOrAwaitValue para escrever a declaração

Nesta etapa, você usa o método getOrAwaitValue e escreve uma instrução assert que verifica se o newTaskEvent foi acionado.

  1. Obtenha o valor LiveData para newTaskEvent usando getOrAwaitValue.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. Afirme que o valor não é nulo.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

O teste completo deve ser semelhante ao código abaixo.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. Execute seu código e observe a passagem do teste!

Agora que você viu como escrever um teste, escreva um sozinho. Nesta etapa, usando as habilidades que você aprendeu, pratique escrever outro teste TasksViewModel.

Etapa 1. Escreva seu próprio teste ViewModel

Você escreverá setFilterAllTasks_tasksAddViewVisible(). Este teste deve verificar se você configurou o tipo de filtro para mostrar todas as tarefas, se o botão Add task está visível.

  1. Usando addNewTask_setsNewTaskEvent() para referência, escreva um teste em TasksViewModelTest chamado setFilterAllTasks_tasksAddViewVisible() que define o modo de filtragem para ALL_TASKS e afirma que o tasksAddViewVisible LiveData é true.


Use o código abaixo para começar.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

Nota:

  1. Faça seu teste.

Etapa 2. Compare seu teste com a solução

Compare sua solução com a solução abaixo.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

Verifique se você faz o seguinte:

Etapa 3. Adicionar uma regra @Before

Observe como, no início de ambos os testes, você define um TasksViewModel.

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Ao repetir o código de configuração para vários testes, você pode usar a anotação @Before para criar um método de configuração e remover o código repetido. Como todos esses testes vão testar o TasksViewModel e precisam de um modelo de vista, mova este código para um bloco @Before.

  1. Crie uma variável de instância lateinit chamada tasksViewModel|.
  2. Crie um método chamado setupViewModel.
  3. Anote com @Before.
  4. Mova o código de instanciação do modelo de vista para setupViewModel.

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. Execute seu código!

Aviso

Não faça o seguinte, não inicialize o

tasksViewModel

com sua definição:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

Isso fará com que a mesma instância seja usada para todos os testes. Isso é algo que você deve evitar porque cada teste deve ter uma nova instância do assunto em teste (o ViewModel neste caso).

Seu código final para TasksViewModelTest deve ser semelhante ao código abaixo.

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

Clique aqui para ver uma diferença entre o código que você iniciou e o código final.

Para baixar o código do tutorial concluído, você pode usar o comando git abaixo:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


Alternativamente, você pode baixar o repositório como um arquivo Zip, descompactá-lo e abri-lo no Android Studio.

Baixe o Zip

Este tutorial cobriu:

Amostras:

Documentação do desenvolvedor Android:

Vídeos:

De outros:

Comece a próxima lição: 5.2 Introdução a teste de dublês e injeção de dependência

Comece a próxima lição: 5.2 Introdução a teste de dublês e injeção de dependência

Para obter links para outros tutoriais neste curso, consulte a página inicial de tutoriais do Android avançado em Kotlin.