1. Antes de começar
Este codelab ensina sobre testes automatizados no Android e como eles permitem que você crie apps escalonáveis e robustos. Você também vai entender a diferença entre a lógica da IU e a lógica de negócios e como elas podem ser testadas. Por fim, você vai aprender a criar e executar testes automatizados no Android Studio.
Pré-requisitos
- Capacidade de criar um app Android com funções e elementos combináveis.
O que você vai aprender
- O que os testes automatizados no Android fazem.
- Por que os testes automatizados são importantes.
- O que é um teste local e para que ele é usado.
- O que é um teste de instrumentação e para que ele é usado.
- Como criar testes locais para o código do Android.
- Como criar testes de instrumentação para apps Android.
- Como executar testes automatizados.
O que você vai criar
- Um teste local.
- Um teste de instrumentação.
O que é necessário
- A versão mais recente do Android Studio.
- O código da solução para o app Tip Time.
2. Acessar o código inicial
Faça o download do código:
Outra opção é clonar o repositório do GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git $ cd basic-android-kotlin-compose-training-tip-calculator $ git checkout main
3. Testes automatizados
Teste, no software, é um método estruturado de verificação do seu software para garantir que ele funcione conforme o esperado. Os testes automatizados são códigos que garantem que outro código que você escreveu funciona corretamente.
Os testes são uma parte importante do processo de desenvolvimento de apps. Executando testes no app de forma consistente, você pode verificar a precisão, o comportamento funcional e a usabilidade dele antes de o lançar publicamente.
Eles também oferecem uma maneira de verificar de forma contínua o código já existente à medida que as mudanças são introduzidas.
Embora os testes manuais quase sempre tenham um lugar garantido, no Android eles podem ser automatizados com frequência. No restante do curso, vamos focar nos testes automatizados para testar o código do app e os requisitos funcionais do app em si. Neste codelab, você vai aprender as noções básicas de testes no Android. Nos próximos, vai aprender práticas mais avançadas de teste de apps Android.
Enquanto você se familiariza com o desenvolvimento e os testes de apps Android, acostume-se a programar os testes junto com o código do app. Criar um teste sempre que um novo recurso for adicionado reduz a carga de trabalho à medida que o app crescer. Essa também é uma maneira conveniente de garantir que o app funcione bem sem gastar muito tempo com testes manuais.
Os testes automatizados são uma parte essencial de todo o desenvolvimento de softwares, e o desenvolvimento para Android não é exceção. Por isso, este é o melhor momento para falar desse assunto.
Por que os testes automatizados são importantes
A princípio, pode parecer que você não precisa fazer testes no seu app, mas isso é necessário para apps de todos os tamanhos e complexidades.
Para aumentar a base de código, é necessário testar a funcionalidade existente conforme você adiciona novas partes. Isso só é possível se você já tiver testes. À medida que seu app cresce, a realização de testes manuais passa a exigir mais esforços do que os testes automatizados. Além disso, ao trabalhar em apps em fase de produção, os testes passam a ser essenciais quando se tem uma grande base de usuários. Por exemplo, é preciso considerar diversos tipos de dispositivos com diferentes versões do Android.
Por fim, chega um momento em que os testes automatizados conseguem dar conta da maioria dos casos de uso de forma significativamente mais rápida do que os testes manuais. Quando você executa testes antes de lançar um código novo, é possível fazer modificações no código para evitar lançar um app com comportamentos inesperados.
Os testes automatizados são executados por software, diferente dos manuais, que são realizados por uma pessoa que interage diretamente com um dispositivo. Os testes automatizados e manuais têm um papel essencial para garantir uma experiência agradável aos usuários do produto. No entanto, os testes automatizados podem ser mais precisos. Eles também otimizam a produtividade da equipe, porque não precisam de uma pessoa para serem feitos e porque podem ser executados de forma muito mais rápida do que um teste manual.
Tipos de testes automatizados
Testes locais
Os testes locais são um tipo de teste automatizado que verifica diretamente uma pequena parte do código para garantir o funcionamento correto. Com testes locais, é possível testar funções, classes e propriedades. Os testes locais são executados na estação de trabalho, o que significa que eles são executados em um ambiente de desenvolvimento sem a necessidade de um dispositivo ou emulador. Essa é uma maneira sofisticada de dizer que os testes locais são executados no seu computador. Como eles também têm uma sobrecarga muito baixa para recursos de computador, podem ser executados rapidamente mesmo com recursos limitados. O Android Studio já vem pronto para executar testes locais automaticamente.
Testes de instrumentação
No desenvolvimento para Android, um teste de instrumentação é um teste de interface. Com os testes de instrumentação, é possível testar partes de um app que dependem da API do Android, além de serviços e APIs da plataforma.
Ao contrário dos testes locais, os de interface iniciam um app ou parte dele, simulam interações do usuário e verificam se o app reagiu corretamente. Neste curso, os testes de IU serão executados em um dispositivo físico ou um emulador.
Quando você executa um teste de instrumentação no Android, o código do teste é integrado ao próprio pacote de aplicativo Android (APK), como um app Android normal. Um APK é um arquivo compactado que contém todo o código e os arquivos necessários para executar o app em um dispositivo ou emulador. O APK de teste é instalado no dispositivo ou emulador junto ao APK normal do app. Em seguida, o APK de teste executa os testes no APK do app.
4. Criar um teste local
Preparar o código do app
Os testes locais testam os métodos diretamente no código do app. Por isso, os métodos a serem testados precisam estar disponíveis para as classes e os métodos de teste. O teste local no snippet de código a seguir garante que o método calculateTip()
funcione corretamente, mas o método calculateTip()
está definido como private
(particular) no momento e, portanto, não pode ser acessado pelo teste. Remova a designação private
e a defina como internal
:
MainActivity.kt
internal fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
var tip = tipPercent / 100 * amount
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
return NumberFormat.getCurrencyInstance().format(tip)
}
- No arquivo
MainActivity.kt
na linha antes do métodocalculateTip()
, adicione a anotação@VisibleForTesting
:
@VisibleForTesting
internal fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
var tip = tipPercent / 100 * amount
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
return NumberFormat.getCurrencyInstance().format(tip)
}
Isso faz com que o método fique público, mas indica a outras pessoas que ele é público apenas para fins de teste.
Criar o diretório de teste
Em projetos para Android, é no diretório test
que os testes locais são gravados.
Criar o diretório de teste:
- Na guia Project, mude a visualização para "Project".
- Clique com o botão direito no diretório src.
- Selecione New > Directory.
- Na janela New Directory, selecione test/java.
- Pressione a tecla de retorno ou Enter no teclado. O diretório test agora pode ser encontrado na guia Project.
O diretório test requer uma estrutura de pacote idêntica à do diretório main
em que o código do app fica. Em outras palavras, assim como o código do app é gravado no pacote main > java > com > example > tiptime, os testes locais são gravados em test > java > com > example > tiptime.
Crie esta estrutura de pacotes no diretório test:
- Clique com o botão direito do mouse no diretório test/java e selecione New > Package.
- Na janela New Package, digite
com.example.tiptime
.
Criar a classe de teste
Agora que o pacote test está pronto, é hora de criar alguns testes. Comece criando a classe de teste.
- Na guia Project, clique em app >src > test e, em seguida, clique na seta ao lado do diretório
test
para abrir. - Clique com o botão direito do mouse no diretório
com.example.tiptime
e selecione New > Kotlin Class/File.
- Digite
TipCalculatorTests
como o nome da classe.
Criar o teste
Como já mencionado, testes locais são usados para testar pequenas partes do código no app. A função principal do app Tip Time calcula gorjetas, então é necessário que haja um teste local para garantir que a lógica de cálculo da gorjeta funcione corretamente.
Para fazer isso, você precisa chamar diretamente a função calculateTip()
, como fez no código do app. Em seguida, garanta que o valor retornado pela função corresponde ao esperado com base nos valores transmitidos para a função.
Há alguns detalhes que você precisa saber sobre a criação de testes automatizados. A lista de conceitos a seguir se aplica aos testes locais e de instrumentação. Eles podem parecer abstratos a princípio, mas você vai aprender mais sobre esse assunto até o fim deste codelab.
- Programe testes automatizados na forma de métodos.
- Adicione a anotação
@Test
ao método. Isso informa ao compilador que o método é de teste para que ele possa ser executado corretamente.
- Verifique se o nome descreve claramente o que é testado e qual é o resultado esperado.
- Os métodos de teste não usam a mesma lógica dos métodos comuns de app. Eles não estão preocupados com a forma como algo é implementado. Eles verificam estritamente uma saída esperada para uma determinada entrada. Ou seja, os métodos de teste executam somente um conjunto de instruções para declarar que a interface ou a lógica de um app funciona corretamente. Você ainda não precisa entender o que isso significa porque poderá conferir o resultado mais tarde, mas lembre-se de que o código do teste pode ser muito diferente do código de app que você costuma usar.
- Os testes geralmente terminam com uma declaração, que é usada para garantir que uma determinada condição foi atendida. As declarações são feitas na forma de uma chamada de método com assert no nome. Por exemplo: a declaração
assertTrue()
é usada com frequência em testes do Android. As instruções de declaração são usadas na maioria dos testes, mas raramente no código do app.
Programe o teste:
- Crie um método para testar o cálculo de uma gorjeta de 20% para uma fatura de US$ 10. O resultado esperado desse cálculo é de US$ 2.
import org.junit.Test
class TipCalculatorTests {
@Test
fun calculateTip_20PercentNoRoundup() {
}
}
Talvez você se lembre de que o método calculateTip()
do arquivo MainActivity.kt
no código do app exige três parâmetros: o valor da fatura, a porcentagem da gorjeta e uma flag para arredondar o resultado ou não.
fun calculateTip(amount: Double, tipPercent: Double, roundUp: Boolean)
Na hora de chamar esse método do teste, esses parâmetros precisam ser transmitidos da mesma forma que eram quando o método era chamado no código do app.
- No método
calculateTip_20PercentNoRoundup()
, crie duas variáveis constantes: umaamount
definida com um valor10.00
e umatipPercent
definida com um valor20.00
.
val amount = 10.00
val tipPercent = 20.00
- No código do app, no arquivo
MainActivity.kt
, observe o código a seguir. O valor da gorjeta é formatado com base na localidade do dispositivo.
MainActivity.kt
...
NumberFormat.getCurrencyInstance().format(tip)
...
A mesma formatação precisa ser usada ao verificar o valor esperado da gorjeta no teste.
- Crie uma variável
expectedTip
definida comoNumberFormat.getCurrencyInstance().format(2)
.
Em seguida, a variável expectedTip
é comparada ao resultado do método calculateTip()
. É assim que o teste garante que o método funciona corretamente. Na última etapa, você definiu a variável amount
com um valor 10.00
e a tipPercent
com um valor 20.00
. 20% de 10 é 2. Portanto, a variável expectedTip
está sendo definida como uma moeda formatada com um valor 2
. Lembre-se de que o método calculateTip()
retorna um valor String
formatado.
- Chame o método
calculateTip()
com as variáveisamount
etipPercent
e transmita um argumentofalse
para o arredondamento.
Neste caso, você não precisa considerar o arredondamento, porque o resultado esperado não o considera.
- Armazene o resultado da chamada do método em uma variável
actualTip
constante.
Até este ponto, programar esse teste não foi muito diferente de programar um método normal no código do app. No entanto, agora que você tem o valor retornado do método que quer testar, é necessário usar uma declaração para determinar se esse é o valor correto.
Fazer uma declaração geralmente é o objetivo final de um teste automatizado e não é algo usado com frequência no código do app. Nesse caso, é importante garantir que a variável actualTip
seja igual à expectedTip
. Para isso, use o método assertEquals()
da biblioteca JUnit
.
O método assertEquals()
usa dois parâmetros: um valor esperado e um valor real. Se eles forem iguais, a declaração e o teste serão aprovados. Se não forem iguais, a declaração e o teste vão falhar.
- Chame esse método
assertEquals()
e transmita as variáveisexpectedTip
eactualTip
como parâmetros:
import org.junit.Assert.assertEquals
import org.junit.Test
import java.text.NumberFormat
class TipCalculatorTests {
@Test
fun calculateTip_20PercentNoRoundup() {
val amount = 10.00
val tipPercent = 20.00
val expectedTip = NumberFormat.getCurrencyInstance().format(2)
val actualTip = calculateTip(amount = amount, tipPercent = tipPercent, false)
assertEquals(expectedTip, actualTip)
}
}
Executar o teste
Agora está na hora de executar o teste.
Talvez você tenha notado que aparecem setas no gutter, ao lado do número da linha do nome da classe e da função de teste. Clique nessas setas para executar o teste. Ao clicar na seta ao lado de um método, você só executa esse método de teste. Se você tem vários métodos de teste em uma classe, clique na seta ao lado dela para executar todos os métodos nessa classe.
Execute o teste:
- Clique nas setas ao lado da declaração de classe e, em seguida, clique em Run 'TipCalculatorTests'.
Vai aparecer algo assim:
- A saída será mostrada na parte de baixo do painel Run.
5. Criar um teste de instrumentação
Criar o diretório de instrumentação
O diretório de instrumentação é criado de forma semelhante ao diretório local de testes.
- Clique com o botão direito do mouse no diretório src e selecione New > Directory.
- Na janela New Directory, selecione androidTest/java.
- Pressione a tecla de retorno ou Enter no teclado. O diretório androidTest agora pode ser encontrado na guia Project.
Assim como os diretórios main
e test
têm a mesma estrutura de pacotes, o diretório androidTest
precisa conter a mesma estrutura de pacotes.
- Clique com o botão direito do mouse na pasta androidTest/java e selecione New > Package.
- Na janela New Package, digite
com.example.tiptime
. - Pressione a tecla de retorno ou Enter no teclado. A estrutura completa de pacotes para o diretório
androidTest
agora pode ser encontrada na guia Project.
Criar a classe de teste
Em projetos para Android, o diretório de teste de instrumentação é designado como o diretório androidTest
.
Para criar um teste de instrumentação, repita o mesmo processo usado para criar um teste local, mas agora faça isso no diretório androidTest
.
Crie a classe de teste:
- Navegue até o diretório
androidTest
no painel do projeto. - Clique nas setas para abrir ao lado de cada diretório até encontrar o diretório
tiptime
.
- Clique com o botão direito do mouse no diretório
tiptime
e selecione New > Kotlin Class/File. - Digite
TipUITests
como o nome da classe.
Criar o teste
O código de teste de instrumentação é muito diferente do código de teste local.
Os testes de instrumentação testam uma instância do app e a interface dela. Portanto, o conteúdo da interface precisa ser definido, de forma semelhante a como você o definiu no método onCreate()
do arquivo MainActivity.kt
quando criou o código para o app Tip Time. É necessário fazer isso antes de programar todos os testes de instrumentação para apps criados com o Compose.
No caso dos teste do app Tip Time, a próxima etapa é escrever instruções para interagir com os componentes da interface. Assim, o processo de cálculo da gorjeta é testado na interface. O conceito de um teste de instrumentação pode parecer abstrato no início, mas não se preocupe. O processo é explicado nas etapas a seguir.
Programe o teste:
- Crie uma variável
composeTestRule
definida como o resultado do métodocreateComposeRule()
e inclua a anotaçãoRule
nela:
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule
class TipUITests {
@get:Rule
val composeTestRule = createComposeRule()
}
- Crie um método
calculate_20_percent_tip()
e adicione a anotação@Test
a ele:
import org.junit.Test
@Test
fun calculate_20_percent_tip() {
}
O compilador sabe que os métodos anotados com @Test
no diretório androidTest
se referem aos testes de instrumentação, e os métodos com a anotação @Test
no diretório test
se referem a testes locais.
- No corpo da função, chame a função
composeTestRule.setContent()
. Isso define o conteúdo da interface dacomposeTestRule
. - No corpo da lambda da função, chame a função
TipTimeTheme()
com um corpo de lambda que chame a funçãoTipTimeLayout()
.
import com.example.tiptime.ui.theme.TipTimeTheme
@Test
fun calculate_20_percent_tip() {
composeTestRule.setContent {
TipTimeTheme {
TipTimeLayout()
}
}
}
Quando terminar, o código vai ser semelhante ao criado para definir o conteúdo no método onCreate()
no arquivo MainActivity.kt
. Agora que o conteúdo da interface está configurado, você pode criar instruções para interagir com os componentes da interface do app. Neste app, você precisa testar se o valor de gorjeta correto é mostrado com base no valor da fatura e nas entradas da porcentagem da gorjeta.
- Os componentes da interface podem ser acessados como nós pela
composeTestRule
. Uma maneira comum de fazer isso é acessar um nó que contém um texto específico com o métodoonNodeWithText()
. Use o métodoonNodeWithText()
para acessar o elemento combinávelTextField
do valor da fatura:
import androidx.compose.ui.test.onNodeWithText
@Test
fun calculate_20_percent_tip() {
composeTestRule.setContent {
TipTimeTheme {
TipTimeLayout()
}
}
composeTestRule.onNodeWithText("Bill Amount")
}
Em seguida, você pode chamar o método performTextInput()
e transmitir o texto que você quer inserir para preencher o elemento de combinável TextField
.
- Preencha o
TextField
com o valor da fatura como10
:
import androidx.compose.ui.test.performTextInput
@Test
fun calculate_20_percent_tip() {
composeTestRule.setContent {
TipTimeTheme {
TipTimeLayout()
}
}
composeTestRule.onNodeWithText("Bill Amount")
.performTextInput("10")
}
- Use a mesma abordagem para preencher o
OutlinedTextField
de porcentagem da gorjeta com um valor20
:
@Test
fun calculate_20_percent_tip() {
composeTestRule.setContent {
TipTimeTheme {
TipTimeLayout()
}
}
composeTestRule.onNodeWithText("Bill Amount")
.performTextInput("10")
composeTestRule.onNodeWithText("Tip Percentage").performTextInput("20")
}
Depois que todos os elementos combináveis TextField
forem preenchidos, a gorjeta vai ser mostrada em um elemento Text
na parte de baixo da tela no app.
Agora que você instruiu o teste a preencher os elementos combináveis TextField
, é necessário garantir que o elemento Text
mostre a gorjeta correta com uma declaração.
Em testes de instrumentação com o Compose, as declarações podem ser chamadas diretamente em componentes da interface. Há várias declarações disponíveis, mas neste caso, é melhor usar o método assertExists()
. É esperado que o elemento combinável Text
mostre o valor da gorjeta desta maneira: Tip Amount: $2.00
.
- Faça uma declaração de que existe um nó com esse texto:
import java.text.NumberFormat
@Test
fun calculate_20_percent_tip() {
composeTestRule.setContent {
TipTimeTheme {
Surface (modifier = Modifier.fillMaxSize()){
TipTimeLayout()
}
}
}
composeTestRule.onNodeWithText("Bill Amount")
.performTextInput("10")
composeTestRule.onNodeWithText("Tip Percentage").performTextInput("20")
val expectedTip = NumberFormat.getCurrencyInstance().format(2)
composeTestRule.onNodeWithText("Tip Amount: $expectedTip").assertExists(
"No node with this text was found."
)
}
Executar o teste
O processo de execução de um teste de instrumentação é o mesmo que o de um teste local. Clique nas setas no gutter ao lado de cada declaração para executar um teste individual ou a classe de teste inteira.
- Clique nas setas ao lado da declaração de classe. É possível conferir a execução dos testes no dispositivo ou emulador. Quando o teste for concluído, a saída mostrada será semelhante a esta:
6. Acessar o código da solução
Outra opção é clonar o repositório do GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git $ cd basic-android-kotlin-compose-training-tip-calculator $ git checkout test_solution
7. Conclusão
Parabéns! Você criou seus primeiros testes automatizados no Android. Os testes são um componente essencial do controle de qualidade de softwares. Ao continuar criando apps Android, programe testes junto aos recursos do seu app para garantir que eles funcionam corretamente durante todo o processo de desenvolvimento.
Resumo
- O que são testes automatizados.
- Por que os testes automatizados são importantes.
- A diferença entre testes locais e de instrumentação.
- Práticas recomendadas para criar testes automatizados.
- Onde encontrar e colocar classes de testes locais e de instrumentação em um projeto Android.
- Como criar um método de teste.
- Como criar classes de teste locais e de instrumentação.
- Como fazer declarações em testes locais e de instrumentação.
- Como usar as regras de teste.
- Como usar a
ComposeTestRule
para iniciar o app com um teste. - Como interagir com elementos combináveis em um teste de instrumentação.
- Como executar testes.