Criar testes automatizados

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étodo calculateTip(), 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:

  1. Na guia Project, mude a visualização para "Project".

b9fac49a80bc59f6.png

  1. Clique com o botão direito no diretório src.

6cdf1a84fd2c0a25.png

  1. Selecione New.

dc9d7b82d65502a3.png

  1. Selecione Directory.

1c9115800a6f8e36.png

  1. Na janela New Directory, selecione test/java.

56f5e2df9525a230.png

  1. Pressione a tecla de retorno ou Enter no teclado. O diretório test agora pode ser encontrado na guia Project.

60c6a44570332cab.png

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:

  1. Clique com o botão direito do mouse no diretório test/java e selecione New > Package.

5814cfecbebd43e1.png

  1. Na janela New Package, digite com.example.tiptime.

74fc5fbc7e051a4c.png

Criar a classe de teste

Agora que o pacote test está pronto, é hora de criar alguns testes. Comece criando a classe de teste.

  1. Na guia Project, clique em app >src > test e, em seguida, clique na seta de expansão 7aeb5945d20f0dd0.png ao lado do diretório test.

exibição da pasta unitTest

  1. Clique com o botão direito do mouse no diretório tiptime e selecione New > Kotlin Class/File.

8c64ee6e43c62481.png

  1. Digite TipCalculatorTests como o nome da classe.

8c39d1d2ac201307.png

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:

  1. 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.

  1. No método calculateTip_20PercentNoRoundup(), crie duas variáveis constantes: uma amount definida com um valor 10.00 e uma tipPercent definida com um valor 20.00.
val amount = 10.00
val tipPercent = 20.00
  1. 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.

  1. Crie uma variável expectedTip definida como NumberFormat.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.

  1. Chame o método calculateTip() com as variáveis amount e tipPercent e transmita um argumento false para o arredondamento.

Neste caso, você não precisa considerar o arredondamento, porque o resultado esperado não o considera.

  1. 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.

  1. Chame esse método assertEquals() e transmita as variáveis expectedTip e actualTip 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.

d1d3291589b08b74.png

Execute o teste:

  • Clique nas setas ao lado da declaração de classe e, em seguida, clique em Run 'TipCalculatorTests'.

301a67db81194d1a.png

O resultado será o seguinte:

  • No gutter, as setas são substituídas por uma marca de seleção verde e um triângulo dc22757efa3bff97.png. Isso significa que o teste foi aprovado.

ecf625f23f30a1bb.png

  • A saída será mostrada na parte de baixo do painel Run.

exibição dos testes aprovados

  • Uma indicação de que os testes foram aprovados.

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.

  1. Clique com o botão direito do mouse no diretório src e selecione New > Directory.

opção de menu do diretório selecionada

  1. Na janela New Directory, selecione androidTest/java.

49b436219213c56d.png

  1. Pressione a tecla de retorno ou Enter no teclado. O diretório androidTest agora pode ser encontrado na guia Project.

pasta de teste android selecionada

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.

  1. Clique com o botão direito do mouse na pasta androidTest/java e selecione New > Package.
  2. Na janela New Package, digite com.example.tiptime.
  3. 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:

  1. Navegue até o diretório androidTest no painel do projeto.

a627f92d40041107.png

  1. Clique nas setas de expansão a30374584d86ddb6.png ao lado de cada diretório até encontrar o diretório tiptime.

7653ebbc899a26a.png

  1. Clique com o botão direito do mouse no diretório tiptime e selecione New > Kotlin Class/File.

69b2c4bcf72c7b1a.png

  1. Digite TipUITests como o nome da classe.

8685533c87fbbea0.png

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:

  1. Crie uma variável composeTestRule definida como o resultado do método createComposeRule() e inclua a anotação Rule nela:
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule

class TipUITests {

   @get:Rule
   val composeTestRule = createComposeRule()
}
  1. 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.

  1. No corpo da função, chame a função composeTestRule.setContent(). Isso define o conteúdo da interface da composeTestRule.
  2. No corpo da lambda da função, chame a função TipTimeTheme() com um corpo de lambda que chame a função TipTimeLayout().
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.

  1. 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étodo onNodeWithText(). Use o método onNodeWithText() para acessar o elemento combinável TextField 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.

  1. Preencha o TextField com o valor da fatura como 10:
import androidx.compose.ui.test.performTextInput

@Test
fun calculate_20_percent_tip() {
    composeTestRule.setContent {
        TipTimeTheme {
            TipTimeLayout()
        }
    }
    composeTestRule.onNodeWithText("Bill Amount")
.performTextInput("10")
}
  1. Use a mesma abordagem para preencher o OutlinedTextField de porcentagem da gorjeta com um valor 20:
@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.

  1. 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.

b435bcafc02c94ef.png

  • 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:

f878f82d3469e877.png

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.