Este codelab faz parte do curso Android avançado no Kotlin. Você aproveitará mais o 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 avançados do Android em Kotlin (link em inglês).
Introdução
Quando você implementou o primeiro recurso do primeiro app, é provável que tenha executado o código para verificar se ele funcionou conforme o esperado. Você fez um teste, mesmo que seja um teste manual. À medida que você continuou a adicionar e atualizar recursos, provavelmente também continuou a executar seu código e verificar se ele funciona. Mas o processo manual é cansativo, propenso a erros e não escalona.
Os computadores são ótimos para escalonamento e automação. Portanto, os desenvolvedores de grandes e pequenas empresas criam testes automatizados, que são executados por software e não exigem que você opere manualmente o app para verificar se o código funciona.
Nesta série de codelabs, você aprenderá a criar um conjunto de testes (conhecido como conjunto de testes) para um app real.
Este primeiro codelab aborda os conceitos básicos de testes no Android. Você criará seus primeiros testes e aprenderá a testar LiveData
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 a arquitetura do app e dos codelabs do curso Conceitos básicos do Android
O que você vai aprender
Você 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ê aprenderá sobre as seguintes bibliotecas e conceitos de código:
- JUnit4.
- Hamcrest (em inglês)
- Biblioteca AndroidX Test
- Biblioteca de testes principais dos Componentes da arquitetura do AndroidX
Atividades do laboratório
- Configurar, executar e interpretar testes locais e instrumentados no Android.
- Crie testes de unidade no Android usando JUnit4 e Hamcrest.
- Crie testes
LiveData
eViewModel
simples.
Nesta série de codelabs, você trabalhará com o app TO-DO Notes. Com ele, você pode anotar as tarefas a serem concluídas e exibi-las em uma lista. Você pode marcá-los como concluídos ou não, filtrá-los ou excluí-los.
Este app é escrito em Kotlin, tem várias telas, usa componentes do Jetpack e segue a arquitetura de um Guia para a arquitetura do app. Ao aprender a testar esse app, você poderá testar apps que usam as mesmas bibliotecas e arquiteturas.
Para começar, faça o download do código:
Como alternativa, é possível clonar o repositório do GitHub para o código:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
Nesta tarefa, você executará o app e explorará a base do código.
Etapa 1: executar o app de exemplo
Depois de fazer o download do app de tarefas, abra-o no Android Studio e execute-o. Ele será compilado. Explore o aplicativo fazendo o seguinte:
- Crie uma nova tarefa com o botão de ação flutuante flutuante. Digite um título primeiro e, em seguida, informações adicionais sobre a tarefa. Salve-a com o FAB de verificação verde.
- Na lista de tarefas, clique no título da tarefa que você acabou de concluir e observe a tela de detalhes dela 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 "Tarefas", abra o menu e filtre as tarefas pelo status Ativo e Concluído.
- Abra a gaveta de navegação e clique em Estatísticas.
- Volte para a tela "Visão geral". No menu da gaveta de navegação, selecione Limpar concluídas para excluir todas as tarefas com o status Concluída.
Etapa 2: analisar o exemplo de código do app
O app de tarefas é baseado no conhecido exemplo de arquitetura e teste Architecture Blueprints, que usa 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 Room. Se você conhece algum dos exemplos abaixo, esse app tem uma arquitetura semelhante:
- Codelab Room com uma visualização
- Codelabs do treinamento do curso Conceitos básicos do Kotlin para Android
- Codelabs de treinamento avançado do Android
- Exemplo do Android Sunflower (link em inglês)
- Curso de treinamento sobre desenvolvimento de apps Android com a Udacity em Kotlin (link em inglês)
É mais importante entender a arquitetura geral do app do que ter um profundo conhecimento da lógica em qualquer camada.
Veja o resumo dos pacotes que você encontrará:
Pacote: | |
| Adicionar ou editar uma tela de tarefas: código da camada de IU para adicionar ou editar uma tarefa. |
| 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 IU para a tela de estatísticas. |
| Tela de detalhes da tarefa:código da camada de IU para uma única tarefa. |
| Tela de tarefas: código da camada de IU para a lista de todas as tarefas. |
| Classes de utilitários: classes compartilhadas usadas em várias partes do app, por exemplo, para o layout de atualização de deslize usado em várias telas. |
Camada de dados (.data)
Esse app inclui uma camada de rede simulada no pacote remoto e uma camada de banco de dados no pacote local. Para simplificar, neste projeto a camada de rede é simulada com apenas um HashMap
com atraso, em vez de fazer solicitações de rede reais.
As coordenadas de DefaultTasksRepository
ou mediam entre a camada de rede e a de banco de dados. É isso que retorna os dados para a camada de IU.
Camada de IU ( .addedittask, .statistics, .taskdetail, .tasks)
Cada um dos pacotes de camada de IU contém um fragmento e um modelo de visualização, além de outras classes necessárias para a IU (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 de navegação. Ela é definida 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 as 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. Conjuntos de origem são pastas que contêm o código-fonte do seu app. Os conjuntos de origem coloridos em verde (androidTest e test) contêm os testes. Ao criar um novo 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 diferentes versões do app que você pode criar (conhecidas como variantes de compilação).androidTest
: contém testes conhecidos como testes de instrumentação.test
: contém testes conhecidos como testes locais.
A diferença entre testes locais e testes de instrumentação está na forma como eles são executados.
Testes locais (test
conjunto de origem)
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, elas correm rapidamente, mas a fidelidade é menor, o que significa que elas funcionam menos como 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 (androidTest
conjunto de origem)
Esses testes são executados em dispositivos Android reais ou emulados e refletem o que acontecerá no mundo real, mas também serão muito mais lentos.
No Android Studio, os testes de instrumentação 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 nele e selecione Run ExampleUnitTest.
Você verá a seguinte saída na janela Run na parte inferior 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. É bom saber que a adição funciona como esperado.
Etapa 2: fazer o teste falhar
Veja abaixo o teste que você acabou de executar.
ExampleUnitTest.kt (link em inglês)
// 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)
}
}
Os testes
- são uma classe em um dos conjuntos de origem de teste.
- contêm funções que começam com a anotação
@Test
(cada função é um único teste). - u8sually contém declarações de declaração.
O Android usa a biblioteca JUnit para realizar testes (neste codelab JUnit4). As declarações e a anotação @Test
vêm do JUnit.
Uma declaração é o núcleo do teste. Essa é uma instrução de código que verifica se o código ou o app se comportam conforme o esperado. Nesse caso, a declaração é assertEquals(4, 2 + 2)
, que verifica que 4 é igual a 2 + 2.
Para ver a aparência de um teste com falha, adicione uma declaração que pode ser vista com facilidade. Verificará 3 se for igual a 1+1.
- Adicione
assertEquals(3, 1 + 1)
ao testeaddition_isCorrect
.
ExampleUnitTest.kt (link em inglês)
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, observe um X ao lado do teste.
- Observe também o seguinte:
- Uma única declaração com falha falhará em todo o teste.
- Você verá o valor esperado (3) em relação ao valor que foi realmente calculado (2).
- Você será direcionado para a linha da declaração
(ExampleUnitTest.kt:16)
reprovada.
Etapa 3: executar um teste de instrumentação
Os testes de instrumentação estão no conjunto de origem androidTest
.
- Abra o conjunto de origem
androidTest
. - Execute o teste chamado
ExampleInstrumentedTest
.
ExampleInstrumentedTest (link em inglês)
@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 de um smartphone Pixel 2 emulado):
Se você tiver um dispositivo conectado ou um emulador em execução, verá o teste ser executado no emulador.
Nesta tarefa, você vai criar testes para o getActiveAndCompleteStats
, que calcula a porcentagem das estatísticas de tarefas ativas e completas do app. É possível ver esses números na tela de estatísticas do app.
Etapa 1: criar uma classe de teste
- No
main
conjunto de origem, abratodoapp.statistics
e abraStatisticsUtils.kt
. - Localize a função
getActiveAndCompletedStats
.
StatisticsUtils.kt (link em inglês)
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 que estão ativas.
O Android Studio oferece ferramentas para gerar stubs de teste e ajudar a implementar os testes para essa função.
- Clique com o botão direito do mouse em
getActiveAndCompletedStats
e selecione Gerar > Testar.
A caixa de diálogo Create Test será aberta:
- Mude o Class name: para
StatisticsUtilsTest
(em vez deStatisticsUtilsKtTest
; é um pouco mais agradável não usar KT no nome da classe de teste). - Mantenha o restante dos padrões. O JUnit 4 é a biblioteca de testes apropriada. O pacote de destino está correto (espelha a localização da classe
StatisticsUtils
) e você não precisa marcar nenhuma das caixas de seleção. Isso gera um código extra, mas você criará seu teste do zero. - Pressione OK.
A caixa de diálogo Choose Destination Directory é aberta:
Você fará um teste local porque a função está fazendo cálculos matemáticos e não incluirá nenhum código específico do Android. Portanto, não é necessário executá-lo em um dispositivo real ou emulado.
- Selecione o diretório
test
(nãoandroidTest
) porque você criará testes locais. - Clique em OK.
- Observe que a classe
StatisticsUtilsTest
gerou emtest/statistics/
.
Etapa 2: criar a primeira função de teste
Você criará um teste que verifica:
- se não houver tarefas concluídas e uma tarefa ativa,
- que a porcentagem de testes ativos é de 100%,
- e a porcentagem de tarefas concluídas é de 0%.
- Abra o
StatisticsUtilsTest
- Crie uma função chamada
getActiveAndCompletedStats_noCompleted_returnsHundredZero
.
StatisticsUtilsTest.kt (link em inglês)
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 ela é 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 esperado, usando declarações.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
Veja o código completo.
StatisticsUtilsTest.kt (link em inglês)
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 Run).
Ele será aprovado em:
Etapa 3: adicionar a dependência do Hamcrest
Como os testes funcionam como uma documentação do que o código faz, é bom quando eles são legíveis. Compare as duas declarações a seguir:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
A segunda declaração é muito mais que uma frase humana. Ela foi escrita usando um framework de declaração chamado Hamcrest. Outra boa ferramenta para criar declarações legíveis é a Truth Library. Neste codelab, você usará o Hamcrest para escrever declaraçõ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 está o testImplementation
. Quando estiver tudo pronto para compartilhar seu app com o mundo, é recomendável não sobrecarregar o tamanho do APK com os códigos ou as dependências do app. É possível designar se uma biblioteca deve ser incluída no código principal ou de teste usando as configurações do Gradle. As configurações mais comuns são as seguintes:
implementation
: a dependência está disponível em todos os conjuntos de origem, incluindo os conjuntos de origem 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
.
Qual configuração você usa 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. Ele também garante que o Hamcrest não seja incluído no app final.
Etapa 4: usar o Hamcrest para escrever declaraçõ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))
É possível usar a importação import org.hamcrest.Matchers.`is`
se solicitado.
O teste final ficará parecido com o código abaixo.
StatisticsUtilsTest.kt (link em inglês)
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 ensinará todos os detalhes do Hamcrest. Se quiser saber mais, confira o tutorial oficial.
Esta é uma tarefa opcional para praticar.
Nesta tarefa, você vai criar mais testes usando JUnit e Hamcrest. Você também gravará testes usando uma estratégia derivada da prática do programa de Desenvolvimento voltado para testes. Desenvolvimento voltado para testes, ou TDD, é uma escola de pensamento de programação que diz que, em vez de escrever o código do recurso primeiro, os testes são criados primeiro. Em seguida, programe o código de 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 tarefa ativa, a porcentagem de
activeTasks
será0f
, e a porcentagem de tarefas concluídas precisará ser100f
. - Se houver duas tarefas concluídas e três tarefas ativas, a porcentagem concluída precisa ser
40f
, e a porcentagem ativa precisa ser60f
.
Etapa 2: Criar um teste para um bug
O código para o getActiveAndCompletedStats
conforme escrito tem um bug. Repare como ela não processa corretamente o que acontece quando a lista está vazia ou é nula. Nos dois 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 programar testes, você usará o desenvolvimento orientado a testes. O teste baseado em testes segue estas etapas.
- Escreva o teste usando a estrutura especificada, quando e depois e com um nome que siga a convenção.
- Confirme se o teste falha.
- Escreva o código mínimo para passar no teste.
- Repita o procedimento para todos os testes.
Em vez de começar a corrigir o bug, primeiro você precisa escrever os testes primeiro. Você pode confirmar se tem testes protegendo você de reintroduzir acidentalmente esses bugs no futuro.
- Se houver uma lista vazia (
emptyList()
), ambas as porcentagens deverão ser 0f. - Se ocorrer 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ê já tem os testes, corrija o bug.
- Corrija o bug no
getActiveAndCompletedStats
retornando0f
setasks
fornull
ou estiver 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 seus testes novamente e confirme se todos eles foram aprovados.
Ao seguir o TDD e criar os testes primeiro, você ajudou a garantir que:
- Os novos recursos sempre têm testes associados. Assim, eles funcionam como uma documentação da função do código.
- Seus testes verificam os resultados corretos e protegem contra bugs que você já viu.
Solução: criar mais testes
Veja todos os testes e o código de recurso correspondente.
StatisticsUtilsTest.kt (link em inglês)
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 (link em inglês)
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
)
}
}
Bom trabalho com as noções básicas de como criar e executar testes. A seguir, você aprenderá a programar testes básicos do ViewModel
e do LiveData
.
No restante do codelab, você aprenderá a programar testes para duas classes do Android comuns na maioria dos apps: ViewModel
e LiveData
.
Comece escrevendo testes para o TasksViewModel
.
Você se concentrará em testes que têm toda a lógica do 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, que aumentam a complexidade do teste. Por enquanto, você vai evitar isso e se concentrar em escrever testes para a funcionalidade do ViewModel que não testam diretamente nada no repositório.
O teste que você criará verificará se você chama o método addNewTask
para abrir a Event
abertura da nova janela de tarefas. Veja 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 realizadas para StatisticsUtilTest
, nesta etapa você criará 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
-> Generate -> Test.
- Na tela Create Test, clique em OK para aceitar. Não é necessário alterar as configurações padrão.
- Na caixa de diálogo Choose Destination Directory, escolha o diretório test.
Etapa 2: Começar a programar seu teste do ViewModel
Nesta etapa, você adicionará um teste de modelo de visualização para testar se, ao chamar o método addNewTask
, o Event
para abrir a nova janela de tarefas é acionado.
- Crie um novo teste com o nome
addNewTask_setsNewTaskEvent
.
TasksViewModelTest.kt (link em inglês)
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?
Quando você cria uma instância de TasksViewModel
para testar, o construtor exige um Application Context. Mas neste teste, você não está criando um aplicativo completo com atividades e IU e fragmentos, então como ter acesso a um contexto do aplicativo?
TasksViewModelTest.kt (link em inglês)
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(???)
As bibliotecas de teste do AndroidX incluem classes e métodos que oferecem versões dos componentes, como aplicativos e atividades, destinados a testes. Quando você tiver um teste local em que precise de classes de framework Android simuladas (como um contexto de aplicativo), siga estas etapas para configurar corretamente o AndroidX Test:
- Adicione as dependências principais e extremas do AndroidX Test.
- Adicionar a dependência da biblioteca Robolectric Testing
- Anotar a classe com o executor de testes do AndroidJunit4
- Criar código do AndroidX Test
Você concluirá essas etapas e então entenderá o que elas fazem juntas.
Etapa 3. Adicionar as dependências do Gradle
- Copie essas dependências no arquivo
build.gradle
do módulo do app para adicionar as dependências principais e externas do AndroidX Test, 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 do JUnit
- Adicione
@RunWith(AndroidJUnit4::class)
acima da classe de teste.
TasksViewModelTest.kt (link em inglês)
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
Etapa 5: Usar o AndroidX Test
Agora, você pode usar a biblioteca de testes AndroidX. Isso inclui o método ApplicationProvider.getApplicationContex
t
, que recebe um contexto de aplicativo.
- Crie um
TasksViewModel
usandoApplicationProvider.getApplicationContext()
da biblioteca de teste do AndroidX.
TasksViewModelTest.kt (link em inglês)
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- Ligar para
addNewTask
notasksViewModel
.
TasksViewModelTest.kt (link em inglês)
tasksViewModel.addNewTask()
Seu teste ficará parecido com o do código abaixo.
TasksViewModelTest.kt (link em inglês)
@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 seu teste para confirmar se ele funciona.
Conceito: como o AndroidX Test funciona?
O que é o AndroidX Test?
O AndroidX Test é uma coleção de bibliotecas para teste. Ele inclui classes e métodos que fornecem versões de componentes, como aplicativos e atividades, destinados a testes. Como exemplo, o código que você escreveu é um exemplo de uma função do AndroidX Test para receber um contexto do aplicativo.
ApplicationProvider.getApplicationContext()
Um dos benefícios das APIs de teste do AndroidX é que elas são criadas para funcionar com testes locais e instrumentados. Isso é bom porque:
- É possível fazer o mesmo teste local ou instrumentado.
- Não é preciso aprender APIs de teste diferentes para testes locais e instrumentados.
Por exemplo, como você escreveu o código usando bibliotecas de teste do AndroidX, pode mover a classe TasksViewModelTest
da pasta test
para a pasta androidTest
. Assim, os testes ainda serão executados. O getApplicationContext()
funciona de maneira um pouco diferente dependendo de ele estar sendo executado como um teste local ou instrumentado:
- Se for um teste instrumentado, ele receberá o contexto real do app fornecido ao inicializar um emulador ou se conectar a um dispositivo real.
- Se for um teste local, ele usará um ambiente simulado do Android.
O que é o Robolectric?
O ambiente simulado do Android que o AndroidX Test usa para testes locais é fornecido pelo Robolectric. A biblioteca Robolectric (link em inglês) cria um ambiente simulado do Android para testes e é executada mais rapidamente do que inicializar um emulador ou executar um dispositivo. Sem a dependência do Robolectric, você receberá este erro:
O que @RunWith(AndroidJUnit4::class)
faz?
Um executor de testes é um componente JUnit que executa testes. Sem um executor, seus testes não seriam executados. Há um executor de testes padrão fornecido pelo JUnit que você recebe automaticamente. @RunWith
substitui esse executor de testes padrão.
O executor de testes AndroidJUnit4
permite que o AndroidX Test execute o teste de maneiras diferentes, dependendo se são testes instrumentados ou locais.
Etapa 6: Corrigir avisos robolétricos
Quando você executar o código, observe que o Robolectric é usado.
Graças ao AndroidX Test e ao executor de testes AndroidJunit4, isso é feito sem que você precise escrever uma única linha de código Robolectric diretamente.
Você pode ver 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 seu arquivo do 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 alerta "WARN: Android SDK 29 requires Java 9..."
é mais complicado. A execução de testes no Android Q requer o Java 9. Em vez de tentar configurar o Android Studio para usar o Java 9, para este codelab, mantenha o SDK de destino e o build em 28.
Resumindo:
- Geralmente, os testes de modelo de visualização puro podem ser inseridos no conjunto de origem
test
porque o código geralmente não exige o Android. - Você pode usar a biblioteca AndroidX test para ver versões de teste de componentes, como apps e atividades.
- Se você precisar executar um código Android simulado no seu conjunto de origem
test
, poderá adicionar 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 ainda não foi concluído. Você ainda não escreveu uma declaração de declaração. Ela diz apenas // TODO test LiveData
. Você aprenderá a escrever instruções de declaração com LiveData
em seguida.
Nesta tarefa, você aprenderá a declarar corretamente o valor do LiveData
.
Aqui você parou sem addNewTask_setsNewTaskEvent
testar o modelo de visualização.
TasksViewModelTest.kt (link em inglês)
@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
, recomendamos que você faça duas coisas:
- Use
InstantTaskExecutorRule
- Garanta a observação em
LiveData
Etapa 1. Usar InstantTaskExecutorRule
InstantTaskExecutorRule
é uma regra JUnit. Quando você o usa com a anotação @get:Rule
, ele faz com que alguns códigos da classe InstantTaskExecutorRule
sejam executados antes e depois dos testes. Para ver o código exato, use o atalho de teclado Command+B para ver o arquivo.
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 aconteçam de maneira síncrona e em uma ordem reproduzível. Ao criar testes que incluam testes de LiveData, use esta regra
- Adicione a dependência do Gradle à biblioteca de testes principais de Componentes da 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 (link em inglês)
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Other code...
}
Etapa 2: Adicionar a classe LiveDataTestUtil.kt
A próxima etapa é verificar se o LiveData
que você está testando é observado.
Ao usar o LiveData
, você normalmente tem uma atividade ou um fragmento (LifecycleOwner
) que observa o LiveData
.
viewModel.resultLiveData.observe(fragment, Observer {
// Observer code here
})
Essa observação é importante. Você precisa de observadores ativos em LiveData
para
- acionar qualquer evento
onChanged
. - Acione todas as transformações.
Para ter o comportamento LiveData
esperado do LiveData
do modelo de visualização, é necessário observar o LiveData
com uma LifecycleOwner
.
Isso apresenta um problema: no teste de TasksViewModel
, não há uma atividade ou um fragmento para observar o LiveData
. Para contornar isso, você pode usar o método observeForever
, que garante que o LiveData
seja observado constantemente, sem precisar de um LifecycleOwner
. Ao observeForever
, você precisa remover seu observador ou correr o risco de vazamento.
O código é semelhante ao código abaixo. Examine-o:
@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)
}
}
Ele é um código boilerplate para observar um único LiveData
em um teste. Há algumas maneiras de se livrar desse código clichê. Você criará uma função de extensão chamada LiveDataTestUtil
para facilitar a adição de observadores.
- Crie um novo arquivo Kotlin com o nome
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. Ela cria uma função de extensão Kotlin chamada getOrAwaitValue
, que adiciona um observador, recebe o valor LiveData
e, em seguida, limpa o observador. Basicamente, é uma versão curta e reutilizável do código observeForever
mostrado acima. Para ver uma explicação completa sobre ela, confira esta postagem do blog.
Etapa 3. Usar getOrAwaitValue para escrever a declaração
Nesta etapa, você usa o método getOrAwaitValue
e escreve uma instrução de declaração que verifica se o newTaskEvent
foi acionado.
- Consiga o valor
LiveData
paranewTaskEvent
usandogetOrAwaitValue
.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- Afirmar que o valor não é nulo.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))
O teste completo ficará como o exemplo 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 passe de teste.
Agora que você já viu como criar um teste, crie um por conta própria. Nesta etapa, usando as habilidades que você aprendeu, escreva outro teste no TasksViewModel
.
Etapa 1. Criar seu próprio teste de ViewModel
Você escreverá setFilterAllTasks_tasksAddViewVisible()
. Esse teste verifica se o botão Adicionar tarefa está visível se você definiu o tipo de filtro para mostrar todas as tarefas.
- Usando
addNewTask_setsNewTaskEvent()
para referência, escreva um teste emTasksViewModelTest
com o nomesetFilterAllTasks_tasksAddViewVisible()
que define o modo de filtragem comoALL_TASKS
e declara que o LiveData dotasksAddViewVisible
étrue
.
Use o código abaixo para começar.
TasksViewModelTest (link em inglês)
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
// When the filter type is ALL_TASKS
// Then the "Add task" action is visible
}
Observação:
- A enumeração
TasksFilterType
para todas as tarefas éALL_TASKS.
- A visibilidade do botão para adicionar uma tarefa é controlada pela
LiveData
tasksAddViewVisible.
.
- Execute o teste.
Etapa 2: Comparar o teste com a solução
Compare sua solução com a solução abaixo.
TasksViewModelTest (link em inglês)
@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))
}
Faça o seguinte:
- Crie
tasksViewModel
usando a mesma instruçãoApplicationProvider.getApplicationContext()
do AndroidX. - Você chama o método
setFiltering
, transmitindo a enumeração do tipo de filtroALL_TASKS
. - Verifique se
tasksAddViewVisible
é verdadeiro usando o métodogetOrAwaitNextValue
.
Etapa 3. Adicionar uma regra @Before
Observe que, no início dos dois testes, você define um TasksViewModel
.
TasksViewModelTest (link em inglês)
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
Quando tiver o código de configuração repetido para vários testes, você poderá 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
com o nometasksViewModel|
. - Crie um método com o nome
setupViewModel
. - Adicione a anotação
@Before
. - Mova o código de instanciação do modelo de visualização para
setupViewModel
.
TasksViewModelTest (link em inglês)
// 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 respectiva 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 objeto sendo testado (o ViewModel, nesse caso).
O código final da TasksViewModelTest
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 iniciado e o código final.
Para fazer o download do 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
Como alternativa, é possível fazer o download do repositório como um arquivo ZIP, descompactá-lo e abri-lo no Android Studio.
Este codelab falou sobre:
- Saber executar testes do Android Studio.
- A diferença entre testes locais (
test
) e de instrumentação (androidTest
). - Como criar testes de unidade locais usando o JUnit e o Hamcrest.
- Configurar testes do ViewModel com a Biblioteca AndroidX Test.
Curso da Udacity:
- Como desenvolver apps Android com Kotlin (link em inglês)
Documentação do desenvolvedor Android:
- Guia para a arquitetura do app
- JUnit4.
- Hamcrest (em inglês)
- Biblioteca Robolectric Testing
- Biblioteca AndroidX Test
- Biblioteca de testes principais dos Componentes da arquitetura do AndroidX
- conjuntos de origem
- Testar na linha de comando
Vídeos:
Outro:
Para ver links de outros codelabs neste curso, consulte a página de destino dos codelabs avançados no Android.