Este codelab faz parte do curso Android avançado no Kotlin. Você vai aproveitar mais este curso se fizer os codelabs em sequência, mas isso não é obrigatório. Todos os codelabs do curso estão listados na página de destino dos codelabs do Android avançado em Kotlin.
Introdução
Ao implementar o primeiro recurso do seu primeiro app, você provavelmente executou o código para verificar se ele funcionava como esperado. Você realizou um teste, embora um teste manual. À medida que você adicionava e atualizava recursos, provavelmente também continuava executando e verificando se o código funcionava. Mas fazer isso manualmente toda vez é cansativo, propenso a erros e não é escalonável.
Os computadores são ótimos para escalonamento e automação. Por isso, desenvolvedores de empresas grandes e pequenas escrevem testes automatizados, que são executados por software e não exigem que você opere o app manualmente para verificar se o código funciona.
Nesta série de codelabs, você vai aprender a criar uma coleção de testes (conhecida como conjunto de testes) para um app do mundo real.
Este primeiro codelab aborda os conceitos básicos de testes no Android. Você vai escrever seus primeiros testes e aprender a testar LiveData
s e ViewModel
s.
O que você já precisa saber
Você precisa:
- A linguagem de programação Kotlin
- As seguintes bibliotecas principais do Android Jetpack:
ViewModel
eLiveData
- Arquitetura de aplicativos, seguindo o padrão do Guia para arquitetura de apps e dos codelabs de Fundamentos do Android
O que você vai aprender
Você vai aprender sobre os seguintes tópicos:
- Como criar e executar testes de unidade no Android
- Como usar o desenvolvimento orientado a testes
- Como escolher testes instrumentados e locais
Você vai aprender sobre as seguintes bibliotecas e conceitos de programação:
- JUnit4
- Hamcrest
- Biblioteca de testes do AndroidX
- Biblioteca de teste principal dos componentes da arquitetura do AndroidX
Atividades deste laboratório
- Configurar, executar e interpretar testes locais e instrumentados no Android.
- Escrever testes de unidade no Android usando JUnit4 e Hamcrest.
- Escreva testes simples de
LiveData
eViewModel
.
Nesta série de codelabs, você vai trabalhar com o app TO-DO Notes. Com ele, é possível anotar tarefas a serem concluídas e mostrá-las em uma lista. Depois, você pode marcar como concluídas ou não, filtrar ou excluir.
Esse app foi escrito em Kotlin, tem várias telas, usa componentes do Jetpack e segue a arquitetura de um Guia para arquitetura de apps. Ao aprender a testar esse app, você poderá testar apps que usam as mesmas bibliotecas e arquitetura.
Para começar, faça o download do código:
Como alternativa, é possível clonar o repositório do GitHub:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
Nesta tarefa, você vai executar o app e analisar a base de código.
Etapa 1: executar o app de exemplo
Depois de baixar o app de tarefas, abra e execute no Android Studio. Ele será compilado. Para explorar o app, faça o seguinte:
- Crie uma tarefa com o botão de ação flutuante de adição. Primeiro, digite um título e depois adicione mais informações sobre a tarefa. Salve com o FAB de marca de seleção verde.
- Na lista de tarefas, clique no título da tarefa que você acabou de concluir e confira a tela de detalhes para ver o restante da descrição.
- Na lista ou na tela de detalhes, marque a caixa de seleção da tarefa para definir o status como Concluída.
- Volte para a tela de tarefas, abra o menu de filtro e filtre as tarefas por status Ativa e Concluída.
- Abra a gaveta de navegação e clique em Estatísticas.
- Volte para a tela de visão geral e, no menu gaveta de navegação, selecione Limpar concluídas para excluir todas as tarefas com o status Concluída.
Etapa 2: conhecer o código do app de exemplo
O app de tarefas pendentes é baseado no popular exemplo de teste e arquitetura Architecture Blueprints (usando a versão de arquitetura reativa do exemplo). O app segue a arquitetura de um Guia para a arquitetura do app. Ele usa ViewModels com fragmentos, um repositório e o Room. Se você conhece algum dos exemplos abaixo, saiba que este app tem uma arquitetura semelhante:
- Codelab Room com uma visualização
- Codelabs de treinamento sobre os conceitos básicos do Kotlin para Android
- Codelabs de treinamento avançado do Android
- Exemplo do Android Sunflower (em inglês)
- Curso de treinamento da Udacity sobre desenvolvimento de apps Android com Kotlin
É mais importante entender a arquitetura geral do app do que ter um conhecimento profundo da lógica em qualquer camada.
Confira o resumo dos pacotes disponíveis:
Pacote: | |
| Tela de adicionar ou editar uma tarefa:código da camada de UI para adicionar ou editar uma tarefa. |
| A camada de dados:lida com a camada de dados das tarefas. Ele contém o código do banco de dados, da rede e do repositório. |
| Tela de estatísticas:código da camada de UI para a tela de estatísticas. |
| Tela de detalhes da tarefa:código da camada de interface para uma única tarefa. |
| Tela de tarefas:código da camada de UI para a lista de todas as tarefas. |
| Classes de utilidade:classes compartilhadas usadas em várias partes do app, por exemplo, para o layout de atualização por deslizar usado em várias telas. |
Camada de dados (.data)
Esse app 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, sendo o que retorna dados para a camada de UI.
Camada de interface ( .addedittask, .statistics, .taskdetail, .tasks)
Cada um dos pacotes da camada de UI contém um fragmento e um modelo de visualização, além de outras classes necessárias para a UI, como um adaptador para a lista de tarefas. O TaskActivity
é a atividade que contém todos os fragmentos.
Navegação
A navegação do app é controlada pelo componente Navigation. Ele é definido no arquivo nav_graph.xml
. A navegação é acionada nos modelos de visualização usando a classe Event
. Os modelos de visualização também determinam quais argumentos transmitir. Os fragmentos observam os Event
s e fazem a navegação real entre as telas.
Nesta tarefa, você vai executar seus primeiros testes.
- No Android Studio, abra o painel Project e encontre estas três pastas:
com.example.android.architecture.blueprints.todoapp
com.example.android.architecture.blueprints.todoapp (androidTest)
com.example.android.architecture.blueprints.todoapp (test)
Essas pastas são conhecidas como conjuntos de origem. Os conjuntos de origem são pastas que contêm o código-fonte do app. Os conjuntos de origem, que são coloridos em verde (androidTest e test), contêm seus testes. Ao criar um projeto Android, você recebe os três conjuntos de origem a seguir por padrão. São eles:
main
: contém o código do app. Esse código é compartilhado entre todas as versões diferentes do app que você pode criar (conhecidas como variantes de build).androidTest
: contém testes conhecidos como testes instrumentados.test
: contém testes conhecidos como testes locais.
A diferença entre testes locais e testes instrumentados está na forma como eles são executados.
Testes locais (conjunto de origem test
)
Esses testes são executados localmente na JVM da máquina de desenvolvimento e não exigem um emulador ou dispositivo físico. Por isso, eles são executados rapidamente, mas a fidelidade é menor, ou seja, 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 de instrumentação (conjunto de origem androidTest
)
Esses testes são executados em dispositivos Android reais ou emulados, então refletem o que vai 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: executar um teste local
- Abra a pasta
test
até encontrar o arquivo ExampleUnitTest.kt. - Clique com o botão direito do mouse e selecione Run ExampleUnitTest.
Você vai ver a seguinte saída na janela Executar na parte de baixo da tela:
- Observe as marcas de seleção verdes e expanda os resultados do teste para confirmar que um teste chamado
addition_isCorrect
foi aprovado. Que bom saber que a adição funciona conforme o esperado!
Etapa 2: fazer o teste falhar
Confira abaixo 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
- são uma classe em um dos conjuntos de origem de teste.
- conter funções que começam com a anotação
@Test
(cada função é um único teste). - geralmente contêm instruções de asserção.
O Android usa a biblioteca de testes JUnit (neste codelab, JUnit4). 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 o código ou app se comportou conforme o esperado. Nesse caso, a declaração é assertEquals(4, 2 + 2)
, que verifica se 4 é igual a 2 + 2.
Para ver como é um teste com falha, adicione uma declaração que você pode ver facilmente que vai falhar. Ele vai verificar se 3 é igual a 1+1.
- Adicione
assertEquals(3, 1 + 1)
ao testeaddition_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
}
}
- Execute o teste.
- Nos resultados do teste, observe um X ao lado dele.
- Observe também:
- Uma única declaração com falha faz com que todo o teste falhe.
- Você recebe o valor esperado (3) e o valor calculado (2).
- Você será direcionado à linha da asserção com falha
(ExampleUnitTest.kt:16)
.
Etapa 3: executar um teste instrumentado
Os testes instrumentados estão no conjunto de origem androidTest
.
- Abra o conjunto de origem
androidTest
. - 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, esse 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, o teste será executado no emulador.
Nesta tarefa, você vai escrever testes para getActiveAndCompleteStats
, que calcula a porcentagem de estatísticas de tarefas ativas e concluídas do seu app. Esses números podem ser vistos na tela de estatísticas do app.
Etapa 1: criar uma classe de teste
- No conjunto de origem
main
, emtodoapp.statistics
, abraStatisticsUtils.kt
. - 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 concluídas e a porcentagem de tarefas ativas.
O Android Studio oferece ferramentas para gerar stubs de teste que ajudam a implementar os testes dessa função.
- Clique com o botão direito do mouse em
getActiveAndCompletedStats
e selecione Generate > Test.
A caixa de diálogo Criar teste é aberta:
- Mude o Nome da classe para
StatisticsUtilsTest
(em vez deStatisticsUtilsKtTest
. É um pouco melhor não ter KT no nome da classe de teste). - Mantenha os demais padrões. O JUnit 4 é a biblioteca de testes adequada. O pacote de destino está correto (ele reflete o local da classe
StatisticsUtils
) e não é necessário marcar nenhuma das caixas de seleção (isso apenas gera código extra, mas você vai escrever seu teste do zero). - Pressione OK.
A caixa de diálogo Escolher diretório de destino é aberta:
Você vai fazer um teste local porque sua função está fazendo cálculos matemáticos e não vai incluir nenhum código específico do Android. Portanto, não é necessário executar em um dispositivo real ou emulado.
- Selecione o diretório
test
(nãoandroidTest
) porque você vai escrever testes locais. - Clique em OK.
- Observe a classe
StatisticsUtilsTest
gerada emtest/statistics/
.
Etapa 2: escrever sua primeira função de teste
Você vai escrever um teste que verifica:
- se não houver tarefas concluídas e uma tarefa ativa,
- que a porcentagem de testes ativos é 100%,
- e a porcentagem de tarefas concluídas é 0%.
- Abra
StatisticsUtilsTest
. - 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
}
}
- Adicione a anotação
@Test
acima do nome da função para indicar que é um teste. - Crie uma lista de tarefas.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- Chame
getActiveAndCompletedStats
com essas tarefas.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- Verifique se
result
é o que você esperava usando asserções.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
Veja 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)
}
}
- Execute o teste (clique com o botão direito do mouse em
StatisticsUtilsTest
e selecione Executar).
Ele precisa passar:
Etapa 3: adicionar a dependência do Hamcrest
Como os testes funcionam como documentação do que o código faz, é bom que eles sejam legíveis para humanos. Compare as duas declarações a seguir:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
A segunda afirmação parece muito mais uma frase humana. Ele é escrito usando uma estrutura de asserção chamada Hamcrest. Outra ferramenta útil para escrever declarações legíveis é a biblioteca Truth. Neste codelab, você vai usar o Hamcrest para escrever asserções.
- 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 app com o mundo, é melhor não aumentar o tamanho do APK com nenhum código de teste ou dependência no app. 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:
implementation
: a dependência está disponível em todos os conjuntos de origem, incluindo os de teste.testImplementation
: a dependência está disponível apenas no conjunto de origem de teste.androidTestImplementation
: a dependência está disponível apenas no conjunto de origemandroidTest
.
A configuração usada define onde a dependência pode ser usada. Se você escrever:
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
Isso significa que o Hamcrest só estará disponível no conjunto de origem de teste. Isso também garante que o Hamcrest não será incluído no app final.
Etapa 4: usar o Hamcrest para escrever asserções
- Atualize o teste
getActiveAndCompletedStats_noCompleted_returnsHundredZero()
para usarassertThat
do Hamcrest em vez deassertEquals
.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
Observação: use a importação import org.hamcrest.Matchers.`is`
se solicitado.
O teste final vai ficar assim:
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))
}
}
- Execute o teste atualizado para confirmar se ele ainda funciona.
Este codelab não vai ensinar todos os detalhes do Hamcrest. Se quiser saber mais, confira o tutorial oficial.
Esta é uma tarefa opcional para praticar.
Nela, você vai escrever mais testes usando JUnit e Hamcrest. Você também vai escrever testes usando uma estratégia derivada da prática de programação de desenvolvimento orientado a testes. O desenvolvimento orientado a testes (TDD) é uma escola de pensamento de programação que diz que, em vez de escrever o código do recurso primeiro, você escreve os testes primeiro. Em seguida, você escreve o código do recurso com o objetivo de passar nos testes.
Etapa 1. Criar testes
Escreva testes para quando você tiver uma lista de tarefas normal:
- Se houver uma tarefa concluída e nenhuma ativa, a porcentagem de
activeTasks
será0f
, e a porcentagem de tarefas concluídas será100f
. - Se houver duas tarefas concluídas e três ativas, a porcentagem concluída será
40f
e a porcentagem ativa será60f
.
Etapa 2. Criar um teste para um bug
O código do getActiveAndCompletedStats
escrito tem um bug. Observe como ele não processa corretamente o que acontece se a lista estiver vazia ou nula. Em ambos os casos, as duas 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ê vai usar o desenvolvimento orientado a testes. O desenvolvimento orientado a testes segue estas etapas.
- Escreva o teste usando a estrutura "Dado, Quando, Então" e com um nome que siga a convenção.
- Confirme se o teste falhou.
- Escreva o mínimo de código para que o teste seja aprovado.
- Repita para todos os testes.
Em vez de começar corrigindo o bug, você vai começar escrevendo os testes primeiro. Assim, você pode confirmar que tem testes que protegem contra a reintrodução acidental desses bugs no futuro.
- Se houver uma lista vazia (
emptyList()
), as duas porcentagens serão 0f. - Se houver um erro ao carregar as tarefas, a lista será
null
, e as duas porcentagens serão 0f. - Execute os testes e confirme se eles falham:
Etapa 3. Corrigir o erro
Agora que você tem os testes, corrija o bug.
- Corrija o bug em
getActiveAndCompletedStats
retornando0f
setasks
fornull
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
)
}
}
- Execute os testes novamente e confirme se todos foram aprovados.
Ao seguir o TDD e escrever os testes primeiro, você ajudou a garantir que:
- Novas funcionalidades sempre têm testes associados. Assim, os testes funcionam como documentação do que o código faz.
- Seus testes verificam os resultados corretos e protegem contra bugs que você já viu.
Solução: escrever mais testes
Confira todos os testes e o código do 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
)
}
}
Você fez um ótimo trabalho com os princípios básicos de criação e execução de testes. Em seguida, você vai aprender a escrever testes básicos de ViewModel
e LiveData
.
No restante do codelab, você vai aprender a escrever testes para duas classes do Android que são comuns na maioria dos apps: ViewModel
e LiveData
.
Comece escrevendo testes para o TasksViewModel
.
Você vai se concentrar em testes que têm toda a lógica no modelo de visualização 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, o que aumenta a complexidade dos testes. Por enquanto, vamos evitar isso e focar na criação de testes para a funcionalidade do ViewModel que não testa diretamente nada no repositório.
O teste que você vai escrever verifica se, ao chamar o método addNewTask
, o Event
para abrir a nova janela de tarefa é acionado. Este é o código do app que você vai testar.
TasksViewModel.kt
fun addNewTask() {
_newTaskEvent.value = Event(Unit)
}
Etapa 1. Criar uma classe TasksViewModelTest
Seguindo as mesmas etapas que você fez para StatisticsUtilTest
, nesta etapa, crie um arquivo de teste para TasksViewModelTest
.
- Abra a classe que você quer testar no pacote
tasks
.TasksViewModel.
- No código, clique com o botão direito do mouse no nome da classe
TasksViewModel
-> Gerar -> Teste.
- Na tela Criar teste, clique em OK para aceitar (não é necessário mudar nenhuma das configurações padrão).
- Na caixa de diálogo Choose Destination Directory, escolha o diretório test.
Etapa 2. Começar a escrever o teste do ViewModel
Nesta etapa, você adiciona um teste de modelo de visualização para verificar se, ao chamar o método addNewTask
, o Event
para abrir a nova janela de tarefa é acionado.
- Crie um 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 o contexto do aplicativo?
Ao criar uma instância de TasksViewModel
para teste, o construtor dela exige um contexto do aplicativo. Mas, neste teste, você não está criando um aplicativo completo com atividades, interface e fragmentos. Então, como você consegue 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 oferecem versões de componentes como aplicativos e atividades destinados a testes. Quando você tiver um teste local em que precisa de classes simuladas do framework Android(como um contexto de aplicativo), siga estas etapas para configurar corretamente o AndroidX Test:
- Adicionar as dependências principais e ext do AndroidX Test
- Adicione a dependência da biblioteca de testes Robolectric.
- Adicione a anotação do executor de testes AndroidJunit4 à classe
- Escrever código do AndroidX Test
Você vai concluir essas etapas e depois entender o que elas fazem juntas.
Etapa 3. Adicionar as dependências do Gradle
- Copie essas dependências para o arquivo
build.gradle
do módulo do app para adicionar as dependências principais do AndroidX Test Core e ext, além da dependência de teste do 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 o executor de testes do JUnit
- Adicione
@RunWith(AndroidJUnit4::class)
acima da classe de teste.
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
Etapa 5. Usar o AndroidX Test
Agora você pode usar a biblioteca AndroidX Test. Isso inclui o método ApplicationProvider.getApplicationContex
t
, que recebe um contexto de aplicativo.
- Crie um
TasksViewModel
usandoApplicationProvider.getApplicationContext()
da biblioteca de testes do AndroidX.
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- Chame
addNewTask
no númerotasksViewModel
.
TasksViewModelTest.kt
tasksViewModel.addNewTask()
Neste ponto, seu teste vai ficar parecido com o 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
}
- Execute o teste para confirmar se ele funciona.
Conceito: como o AndroidX Test funciona?
O que é o AndroidX Test?
O AndroidX Test é um conjunto de bibliotecas para testes. Ele inclui classes e métodos que oferecem versões de componentes como aplicativos e atividades, que são destinados a testes. Por exemplo, este código que você escreveu é um exemplo de uma função do AndroidX Test para receber um contexto de aplicativo.
ApplicationProvider.getApplicationContext()
Um dos benefícios das APIs AndroidX Test é que elas foram criadas para funcionar tanto em testes locais quanto em testes instrumentados. Isso é bom porque:
- É possível executar o mesmo teste como um teste local ou instrumentado.
- Não é necessário aprender diferentes APIs de teste para testes locais e instrumentados.
Por exemplo, como você escreveu o código usando as bibliotecas do AndroidX Test, é possível mover a classe TasksViewModelTest
da pasta test
para a pasta androidTest
, e 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:
- Se for um teste instrumentado, ele vai receber o contexto real do aplicativo fornecido quando um emulador é inicializado ou um dispositivo real é conectado.
- Se for um teste local, ele usará um ambiente Android simulado.
O que é o Robolectric?
O ambiente simulado do Android usado pelo AndroidX Test para testes locais é fornecido pelo Robolectric. O Robolectric é uma biblioteca que cria um ambiente Android simulado para testes e é executada mais rápido do que iniciar um emulador ou executar em um dispositivo. Sem a dependência do Robolectric, você vai receber este erro:
O que @RunWith(AndroidJUnit4::class)
faz?
Um executor de testes é um componente do JUnit que executa testes. Sem um executor de testes, seus testes não seriam executados. Há um executor de testes padrão fornecido pelo JUnit que você recebe automaticamente. O @RunWith
substitui esse executor de testes padrão.
O executor de testes AndroidJUnit4
permite que o AndroidX Test execute seus testes de maneira diferente, dependendo se eles são instrumentados ou locais.
Etapa 6: Corrigir avisos do Robolectric
Ao executar o código, observe que o Robolectric é usado.
Devido ao AndroidX Test e ao executor de testes AndroidJunit4, isso é feito sem que você escreva diretamente uma única linha de código do Robolectric.
Talvez você veja dois avisos.
No such manifest file: ./AndroidManifest.xml
"WARN: Android SDK 29 requires Java 9..."
Para corrigir o aviso No such manifest file: ./AndroidManifest.xml
, atualize o arquivo gradle.
- Adicione a seguinte linha ao arquivo gradle para que o manifesto correto do Android seja usado. A opção includeAndroidResources permite acessar recursos do Android nos testes de unidade, incluindo o 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. Para executar testes no Android Q, é necessário o Java 9. Em vez de tentar configurar o Android Studio para usar o Java 9, mantenha o SDK de destino e de compilação em 28 neste codelab.
Em resumo:
- Os testes de modelo de visualização pura geralmente podem ser incluídos no conjunto de origem
test
porque o código deles não costuma exigir o Android. - Você pode usar a bibliotecaAndroidX test para receber versões de teste de componentes como aplicativos e atividades.
- Se você precisar executar código Android simulado no conjunto de origem
test
, adicione a dependência do Robolectric e a anotação@RunWith(AndroidJUnit4::class)
.
Parabéns! Você está usando a biblioteca de testes do AndroidX e o Robolectric para executar um teste. Seu teste não está concluído (você ainda não escreveu uma instrução assert, apenas // TODO test LiveData
). Em seguida, você vai aprender a escrever instruções assert com LiveData
.
Nesta tarefa, você vai aprender a declarar o valor LiveData
corretamente.
Foi aqui que você parou sem o teste do modelo de visualização 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 LiveData
, recomendamos que você faça duas coisas:
- Usar
InstantTaskExecutorRule
- Garantir a observação de
LiveData
Etapa 1. Usar InstantTaskExecutorRule
InstantTaskExecutorRule
é uma regra do JUnit. Quando usado com a anotação @get:Rule
, ele faz com que parte do código na classe InstantTaskExecutorRule
seja executada antes e depois dos testes. Para ver o código exato, use o atalho de teclado Command+B.
Essa regra executa todos os jobs em segundo plano relacionados aos componentes de arquitetura na mesma linha de execução para que os resultados do teste ocorram de forma síncrona e em uma ordem repetível. Use esta regra ao escrever testes que incluem o LiveData.
- Adicione a dependência do Gradle para a biblioteca de testes principais dos componentes de arquitetura (que contém essa regra).
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- Abrir
TasksViewModelTest.kt
- Adicione o
InstantTaskExecutorRule
à classeTasksViewModelTest
.
TasksViewModelTest.kt
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Other code...
}
Etapa 2. Adicionar a classe LiveDataTestUtil.kt
A próxima etapa é garantir que o LiveData
que você está testando seja observado.
Ao usar LiveData
, é comum ter uma atividade ou um fragmento (LifecycleOwner
) observando o LiveData
.
viewModel.resultLiveData.observe(fragment, Observer {
// Observer code here
})
Essa observação é importante. Você precisa de observadores ativos no LiveData
para
- acionar eventos
onChanged
. - acionar Transformações.
Para ter o comportamento LiveData
esperado para o LiveData
do modelo de visualização, observe o LiveData
com um LifecycleOwner
.
Isso causa um problema: no teste de TasksViewModel
, você não tem uma atividade ou um fragmento para observar o LiveData
. Para evitar isso, use o método observeForever
, que garante que o LiveData
seja constantemente observado, sem precisar de um LifecycleOwner
. Ao observeForever
, não se esqueça de remover o observador para evitar um vazamento.
Isso fica parecido com o código abaixo. Analise:
@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)
}
}
É muito código boilerplate para observar um único LiveData
em um teste. Há algumas maneiras de se livrar desse texto padrão. Você vai criar uma função de extensão chamada LiveDataTestUtil
para simplificar a adição de observadores.
- Crie um arquivo Kotlin chamado
LiveDataTestUtil.kt
no conjunto de origemtest
.
- 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
}
Esse é um método bastante complicado. Ele cria uma função de extensão do Kotlin chamada getOrAwaitValue
, que adiciona um observador, recebe o valor LiveData
e limpa o observador. Basicamente, é uma versão curta e reutilizável do código observeForever
mostrado acima. Para uma explicação completa dessa classe, confira esta postagem do blog (em inglês).
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.
- Receba o valor
LiveData
denewTaskEvent
usandogetOrAwaitValue
.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- Afirma que o valor não é nulo.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))
O teste completo vai ficar parecido com o 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()))
}
}
- Execute o código e veja o teste ser aprovado.
Agora que você já sabe como escrever um teste, crie um por conta própria. Nesta etapa, usando as habilidades que você aprendeu, pratique escrever outro teste TasksViewModel
.
Etapa 1. Criar seu próprio teste do ViewModel
Você vai escrever setFilterAllTasks_tasksAddViewVisible()
. Esse teste precisa verificar se o botão Adicionar tarefa está visível quando o tipo de filtro está definido para mostrar todas as tarefas.
- Usando
addNewTask_setsNewTaskEvent()
como referência, escreva um teste emTasksViewModelTest
chamadosetFilterAllTasks_tasksAddViewVisible()
que define o modo de filtragem comoALL_TASKS
e afirma que o LiveDatatasksAddViewVisible
é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
}
Observação:
- O enumerador
TasksFilterType
para todas as tarefas éALL_TASKS.
. - A visibilidade do botão para adicionar uma tarefa é controlada pelo
LiveData
tasksAddViewVisible.
- Execute o teste.
Etapa 2. Compare seu teste com a solução
Compare sua solução com a 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:
- Crie seu
tasksViewModel
usando a mesma instruçãoApplicationProvider.getApplicationContext()
do AndroidX. - Você chama o método
setFiltering
, transmitindo a enumeração do tipo de filtroALL_TASKS
. - Você verifica se o
tasksAddViewVisible
é verdadeiro usando o métodogetOrAwaitNextValue
.
Etapa 3. Adicionar uma regra @Before
Observe como, no início dos dois testes, você define um TasksViewModel
.
TasksViewModelTest
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
Quando você tem um código de configuração repetido para vários testes, 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 visualização, mova esse código para um bloco @Before
.
- Crie uma variável de instância
lateinit
chamadatasksViewModel|
. - Crie um método com o nome
setupViewModel
. - Anote-o com
@Before
. - Mova o código de instanciação do modelo de visualização para
setupViewModel
.
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- Execute o código.
Aviso
Não faça o seguinte: não inicialize o
tasksViewModel
com a 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 precisa ter uma nova instância do sujeito em teste (o ViewModel, neste caso).
O código final de TasksViewModelTest
vai ficar assim:
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 inicial e o final.
Para baixar o código do codelab concluído, use o comando git abaixo:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Este codelab abordou:
- Como executar testes no Android Studio.
- A diferença entre testes locais (
test
) e de instrumentação (androidTest
). - Como criar testes de unidade locais usando JUnit e Hamcrest.
- Configurar testes de ViewModel com a Biblioteca de testes do AndroidX.
Curso da Udacity:
Documentação do desenvolvedor Android:
- Guia para a arquitetura do app
- JUnit4
- Hamcrest
- Biblioteca de testes Robolectric
- Biblioteca de testes do AndroidX
- Biblioteca de teste principal dos componentes da arquitetura do AndroidX
- conjuntos de origem
- Testar na linha de comando
Vídeos:
Outro:
Para acessar links de outros codelabs deste curso, consulte a página inicial dos codelabs do curso Android avançado no Kotlin.