Introdução aos testes de unidade do Apex
Objetivos de aprendizagem
Após concluir esta unidade, você estará apto a:
- Descrever os principais benefícios dos testes de unidade do Apex.
- Definir uma classe com métodos de teste.
- Executar todos os métodos de teste de uma classe e inspecionar falhas.
- Criar e executar um conjunto de classes de teste.
Testes de unidade do Apex
A estrutura de testes do Apex permite que você escreva e execute testes para suas classes e acionadores do Apex no Lightning Platform. Os testes de unidade do Apex garantem alta qualidade para seu código do Apex e lhe permitem cumprir os requisitos para implantação do Apex.
O teste é o segredo do desenvolvimento bem-sucedido em longo prazo e um componente essencial do processo de desenvolvimento. Com a estrutura de testes do Apex é mais fácil testar seu código do Apex. O código do Apex só pode ser escrito em um ambiente sandbox ou em uma organização do desenvolvedor, não na produção. O código do Apex pode ser implantado em uma organização de produção a partir de um sandbox. Além disso, desenvolvedores de aplicativos podem distribuir o código do Apex aos clientes de suas organizações de desenvolvedores carregando os pacotes para a Lightning Platform AppExchange. Além de serem fundamentais para a garantia de qualidade, os testes de unidade do Apex também são requisitos para implantar e distribuir o Apex.
Estes são os benefícios dos testes de unidade do Apex.
- Garantir que suas classes do Apex e acionadores funcionem conforme previsto
- Ter um conjunto de testes de regressão que pode ser executado novamente sempre que as classes e os acionadores forem atualizados, a fim de garantir que as futuras atualizações que você vier a fazer ao seu aplicativo não interrompam a funcionalidade existente
- Cumprir os requisitos de cobertura de código para a implantação do Apex na produção ou distribuição do Apex a clientes por meio de pacotes
- Aplicativos de alta qualidade entregues à organização de produção, o que torna os usuários de produção mais produtivos
- Aplicativos de alta qualidade entregues aos assinantes de pacotes, o que aumenta a confiança de seus clientes
Requisito de cobertura de código para implantação
Antes de implantar seu código ou empacotá-lo para a Lightning Platform AppExchange, pelo menos 75% do código do Apex deve ser coberto por testes, e todos esses testes devem ser bem sucedidos. Além disso, cada acionador deve ter uma cobertura. Embora a cobertura de código seja um requisito para a implantação, não escreva testes apenas para atender a essa exigência. Teste os casos de uso comuns em seu aplicativo, incluindo casos positivos e negativos do teste, bem como o processamento em massa ou de um só registro.
Sintaxe do método de teste
Os métodos de teste são definidos usando a anotação @isTest
e têm a seguinte sintaxe:
@isTest static void testName() { // code_block }
A anotação @isTest
utiliza vários modificadores entre parênteses e separados por espaços em branco. Abordaremos esse tipo de parâmetro mais tarde.
A visibilidade de um método de teste não importa, por isso, declarar um método de teste como público ou privado não faz diferença, pois a estrutura de testes é sempre capaz de acessar métodos de teste. Por essa razão, os modificadores de acesso são omitidos na sintaxe.
Os métodos de teste devem ser definidos em classes de teste, que são classes anotadas com @isTest
. Esta classe de exemplo mostra uma definição de uma classe de teste com um método de teste.
@isTest private class MyTestClass { @isTest static void myTest() { // code_block } }
As classes de teste podem ser públicas ou privadas. Se você estiver usando uma classe de teste apenas para testes de unidade, declare-a como privada. As classes de teste públicas normalmente são utilizadas para classes de fábrica de dados de teste, sobre as quais falaremos mais tarde.
Exemplo de teste de unidade: Testar a classe TemperatureConverter
O exemplo simples a seguir pertence a uma classe de teste com três métodos de teste. O método de classe que está sendo testado possui uma temperatura em Fahrenheit como entrada. Ele converte essa temperatura para Celsius e informa o resultado convertido. Vamos adicionar a classe personalizada e sua classe de teste.
- No Developer Console, clique em File (Arquivo) | New (Novo) | Apex Class (Classe do Apex), digite
TemperatureConverter
como nome da classe e, em seguida, clique em OK.
- Substitua o corpo de classe padrão pelo seguinte.
public class TemperatureConverter { // Takes a Fahrenheit temperature and returns the Celsius equivalent. public static Decimal FahrenheitToCelsius(Decimal fh) { Decimal cs = (fh - 32) * 5/9; return cs.setScale(2); } }
- Pressione Ctrl+S para salvar sua classe.
- Repita as etapas anteriores para criar a classe
TemperatureConverterTest
. Adicione o seguinte para esta classe.@isTest private class TemperatureConverterTest { @isTest static void testWarmTemp() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(70); System.assertEquals(21.11,celsius); } @isTest static void testFreezingPoint() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(32); System.assertEquals(0,celsius); } @isTest static void testBoilingPoint() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212); System.assertEquals(100,celsius,'Boiling point temperature is not expected.'); } @isTest static void testNegativeTemp() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(-10); System.assertEquals(-23.33,celsius); } }
A classe de teste TemperatureConverterTest
verifica se o método funciona conforme previsto, chamando-o com diferentes entradas para a temperatura em Fahrenheit. Cada método de teste verifica um tipo de entrada: uma temperatura quente, a temperatura do ponto de congelamento, a temperatura do ponto de ebulição e uma temperatura negativa. As verificações são realizadas chamando-se o método System.assertEquals()
, que utiliza dois parâmetros: o primeiro é o valor previsto, e o segundo é o valor real. Há uma outra versão desse método que utiliza um terceiro parâmetro – uma string que descreve a comparação que está sendo executada, que é utilizada em testBoilingPoint()
. Essa string opcional é registrada caso a declaração falhe.
Vamos executar os métodos dessa classe.
- No Developer Console, clique em Test (Teste) | New Run (Nova execução).
- Em Test Classes (Classes de teste), clique em TemperatureConverterTest.
- Para adicionar todos os métodos de teste na classe
TemperatureConverterTest
para a execução de teste, clique em Add Selected (Adicionar selecionado).
- Clique em Run (Executar).
- Na guia Tests (Testes), você verá o status de seus testes durante o processo de execução. Expanda a execução de teste e expanda novamente até que possa ver a lista de testes individuais que foram executados. Todos eles têm marcas de seleção em verde.
Depois de executar os testes, a cobertura de código é gerada automaticamente para as classes e acionadores do Apex presentes na organização. Você pode verificar a percentagem de cobertura de código na guia Tests (Testes) do Developer Console. Neste exemplo, a classe TemperatureConverter
que você testou tem uma cobertura de 100%, conforme mostrado nesta imagem.
Embora um método de teste teria resultado em uma cobertura total da classe TemperatureConverter
, ainda é importante testar diferentes entradas para garantir a qualidade do seu código. Obviamente, não é possível verificar todos os pontos de dados, mas você pode realizar testes em pontos de dados comuns e diferentes intervalos de entrada. Por exemplo, você pode verificar a aprovação de números positivos e negativos, valores-limite e valores de parâmetros inválidos para verificar o comportamento negativo. Os testes para a classe TemperatureConverter
verificam os pontos de dados comuns, como a temperatura de ebulição e temperaturas negativas.
A classe de teste TemperatureConverterTest
não abrange entradas inválidas ou condições-limite. As condições-limite se referem aos valores mínimos e máximos. Nesse caso, o método de conversão da temperatura aceita um Decimal
, que pode aceitar números grandes, superiores a valores Double
(Duplos). Para entradas inválidas, não há temperatura inválida, mas a única entrada inválida é null. Como o método de conversão lida com esse valor? Nesse caso, quando o tempo de execução do Apex desreferencia a variável do parâmetro para avaliar a fórmula, ele lança um System.NullPointerException
. Você pode modificar o método FahrenheitToCelsius()
para verificar se há uma entrada inválida e, nesse caso, retornar null e, em seguida, adicionar um teste para verificar o comportamento da entrada inválida.
Até este ponto, todos os testes são aprovados porque a fórmula de conversão utilizada no método de classe é correta. Mas isso é aborrecido! Tentaremos simular uma falha apenas para ver o que acontece quando uma declaração falha. Por exemplo, vamos modificar o teste de temperatura de ponto de ebulição e aprovar um valor falso previsto para a temperatura de ponto de ebulição em graus Celsius (0 em vez de 100). Isso faz com que o método de teste correspondente falhe.
- Altere o método de teste
testBoilingPoint()
para o seguinte.@isTest static void testBoilingPoint() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212); // Simulate failure System.assertEquals(0,celsius,'Boiling point temperature is not expected.'); }
- Para executar o mesmo teste, clique na última execução na guia Tests (Testes) e clique em Test (Testar) | Rerun (Executar novamente). A afirmação em
testBoilingPoint()
falha e lança um erro fatal (umAssertException
que não pode ser pego).
- Verifique os resultados na guia Tests (Testes), expandindo a última execução de teste. A execução de teste informa a falha de um em cada quatro testes. Para obter mais detalhes sobre a falha, clique duas vezes na execução de teste. Os resultados detalhados aparecem em uma guia separada, conforme mostrado nesta imagem.
[Alt text: No Developer Console, inspecione os resultados de um teste com falha]
- Para obter a mensagem de erro da falha do teste, clique duas vezes dentro da coluna Errors (Erros) para acessar o teste com falha. Você verá o seguinte: o texto descritivo próximo a
Assertion Failed:
(Falha na asserção:) é o texto que fornecemos na instruçãoSystem.assertEquals()
.System.AssertException:Assertion Failed:Boiling point temperature is not expected.:Expected:0, Actual:100.00
(System.AssertException:Falha na asserção:A temperatura de ponto de ebulição não é esperada.:Esperada:0, Real:100.00)
Os dados de teste nesses métodos de teste são números e não registros do Salesforce. Na próxima unidade, você aprenderá mais sobre como testar os registros do Salesforce e como configurar seus dados.
Aumente sua cobertura de código
Ao escrever testes, tente alcançar a maior cobertura de código possível. Não basta apontar para uma cobertura de 75%, que é a menor cobertura que a Lightning Platform exige para implantações e pacotes. Quanto mais casos de teste seus testes cobrirem, maior a probabilidade de o seu código ser robusto. Às vezes, mesmo depois de escrever métodos de teste para todos os seus métodos de classe, a cobertura de código não chega a 100%. Uma causa comum é não cobrir todos os valores de dados referentes à execução de código condicional. Por exemplo, alguns valores de dados tendem a ser ignorados quando seu método de classe possui instruções if que fazem com que diferentes ramificações sejam executadas desde que a condição avaliada seja atendida. Garanta que seus métodos de teste tenham em consideração esses valores diferentes.
Esse exemplo inclui o método de classe getTaskPriority()
, que contém duas instruções if
. A principal tarefa deste método é retornar um valor de string prioritário com base no estado de lead informado. O método valida primeiramente o estado e retorna null se ele for inválido. Se o estado for CA, o método retorna 'High'; caso contrário, retorna 'Normal' para qualquer outro valor de estado.
public class TaskUtil { public static String getTaskPriority(String leadState) { // Validate input if(String.isBlank(leadState) || leadState.length() > 2) { return null; } String taskPriority; if(leadState == 'CA') { taskPriority = 'High'; } else { taskPriority = 'Normal'; } return taskPriority; } }
Esta é a classe de teste para o método getTaskPriority()
. O método de teste simplesmente chama getTaskPriority()
com um estado ('NY').
@isTest private class TaskUtilTest { @isTest static void testTaskPriority() { String pri = TaskUtil.getTaskPriority('NY'); System.assertEquals('Normal', pri); } }
Vamos executar esta classe de teste (TaskUtilTest
) no Developer Console e verificar a cobertura de código para a classe TaskUtil
correspondente que este teste cobre. Após o término da execução do teste, a cobertura de código para TaskUtil
é mostrada como 75%. Se abrir esta classe no Developer Console, verá seis linhas azuis (cobertas) e duas linhas vermelhas (descobertas), conforme mostrado nesta imagem.
A linha 5 não foi coberta porque a nossa classe de teste não continha um teste para aprovar um parâmetro de estado inválido. Da mesma forma, a linha 11 não foi coberta porque o método de teste não aprovou 'CA' como estado. Vamos adicionar mais dois métodos de teste para cobrir esses cenários. A seguir, apresentamos a classe de teste completa após adicionarmos os métodos de teste testTaskHighPriority()
e testTaskPriorityInvalid()
. Se executar novamente essa classe de teste usando Run All (Executar todos) ou New Run (Nova execução), a cobertura de código para TaskUtil
agora será de 100%!
@isTest private class TaskUtilTest { @isTest static void testTaskPriority() { String pri = TaskUtil.getTaskPriority('NY'); System.assertEquals('Normal', pri); } @isTest static void testTaskHighPriority() { String pri = TaskUtil.getTaskPriority('CA'); System.assertEquals('High', pri); } @isTest static void testTaskPriorityInvalid() { String pri = TaskUtil.getTaskPriority('Montana'); System.assertEquals(null, pri); } }
Criar e executar um conjunto de testes
Um conjunto de testes é um conjunto de classes de teste do Apex que você executa juntas. Por exemplo, crie um conjunto de testes para ser executado sempre que você preparar uma implantação ou que a Salesforce liberar uma nova versão. Configure um conjunto de testes no Developer Console para definir um conjunto de classes de teste que são executadas juntas regularmente.
Agora você tem duas classes de teste em sua organização. Essas duas classes não estão relacionadas, mas por ora vamos fingir que estão. Suponhamos que haja situações em que você deseja executar essas duas classes de teste, mas não deseja executar todos os testes em sua organização. Crie um conjunto de testes que contenha ambas as classes e, em seguida, execute os testes no conjunto.
- No Developer Console, selecione Test (Teste) | New Suite (Novo conjunto).
- Insira
TempConverterTaskUtilSuite
como nome do conjunto e, em seguida, clique em OK.
- Selecione TaskUtilTest, mantenha a tecla Ctrl pressionada e, em seguida, selecione TemperatureConverterTest.
- Para adicionar as classes de teste selecionadas para o conjunto, clique em >.
- Clique em Salvar.
- Selecione Test (Teste) | New Suite Run (Execução de novo conjunto).
- Selecione TempConverterTaskUtilSuite e, em seguida, clique em > para mover
TempConverterTaskUtilSuite
até a coluna Selected Test Suites (Conjuntos de testes selecionados).
- Clique em Run Suites (Executar conjuntos).
- Na guia Tests (Testes), monitore o status de seus testes durante o processo de execução. Expanda a execução de teste e expanda novamente até que possa ver a lista de testes individuais que foram executados. Como em uma execução de métodos de teste individuais, você pode clicar duas vezes nos nomes dos métodos para ver os resultados dos testes detalhados.
Criar dados de teste
Os registros do Salesforce criados em métodos de teste não estão comprometidos com o banco de dados. Eles são revertidos quando a execução do teste é concluída. Esse comportamento de reversão é útil para testes porque você não tem que limpar seus dados de teste após o teste ser executado.
Por padrão, os testes do Apex não têm acesso aos dados preexistentes na organização, exceto para acesso a objetos de configuração e metadados, tais como os objetos Usuário ou Perfil. Configure os dados de teste dos seus testes. A criação de dados de teste deixa seus testes mais robustos e evita falhas causadas pela falta de dados ou por dados alterados na organização. Você pode criar dados de teste diretamente em seu método de teste ou utilizando uma classe de utilitário de teste, como veremos mais tarde.
Quero saber mais
- Você pode usar a Extensão Apex do Salesforce para o Visual Studio Code para executar testes do Apex e verificar a funcionalidade do seu código.
- Você pode salvar até 6 MB de código do Apex em cada organização. As classes de teste anotadas com
@isTest
não contam para esse limite.
- Embora os dados de teste sejam revertidos, nenhum banco de dados à parte é utilizado para testes. Como resultado, para alguns sObjects que têm campos com restrições exclusivas, a inserção de registros duplicados sObject resulta em um erro.
- Os métodos de teste não enviam emails.
- Os métodos de teste não podem fazer callouts para serviços externos. Você pode usar callouts simulados nos testes.
- As pesquisas SOSL realizadas em um teste retornam resultados vazios. Para garantir resultados previsíveis, utilize
Test.setFixedSearchResults()
para definir os registros a serem retornados pela pesquisa.
Recursos
-
Guia do desenvolvedor do Apex: Testing Best Practices
-
Guia do desenvolvedor do Apex: What are Apex Unit Tests?
-
Guia do desenvolvedor do Apex: Isolation of Test Data from Organization Data in Unit Tests
-
Ajuda do Salesforce: Checking Code Coverage
Preparar-se para o desafio prático
Para completar o desafio prático desta unidade, você precisará criar uma nova classe do Apex chamada VerifyDate
com o código copiado abaixo:
public class VerifyDate { //method to handle potential checks against two dates public static Date CheckDates(Date date1, Date date2) { //if date2 is within the next 30 days of date1, use date2. Otherwise use the end of the month if(DateWithin30Days(date1,date2)) { return date2; } else { return SetEndOfMonthDate(date1); } } //method to check if date2 is within the next 30 days of date1 private static Boolean DateWithin30Days(Date date1, Date date2) { //check for date2 being in the past if( date2 < date1) { return false; } //check that date2 is within (>=) 30 days of date1 Date date30Days = date1.addDays(30); //create a date 30 days away from date1 if( date2 >= date30Days ) { return false; } else { return true; } } //method to return the end of the month of a given date private static Date SetEndOfMonthDate(Date date1) { Integer totalDays = Date.daysInMonth(date1.year(), date1.month()); Date lastDay = Date.newInstance(date1.year(), date1.month(), totalDays); return lastDay; } }