Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Gorjeta
Este conteúdo é um excerto do eBook, Enterprise Application Patterns Using .NET MAUI, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.
As aplicações multiplataforma enfrentam problemas semelhantes às aplicações de ambiente de trabalho e baseadas na Web. Os usuários móveis serão diferentes por seus dispositivos, conectividade de rede, disponibilidade de serviços e vários outros fatores. Portanto, os aplicativos multiplataforma devem ser testados, pois seriam usados no mundo real para melhorar sua qualidade, confiabilidade e desempenho. Muitos tipos de teste devem ser executados em um aplicativo, incluindo teste de unidade, teste de integração e teste de interface do usuário. O teste de unidade é a forma mais comum e essencial para a construção de aplicações de alta qualidade.
Um teste de unidade usa uma pequena unidade do aplicativo, normalmente um método, isola-a do restante do código e verifica se ela se comporta conforme o esperado. Seu objetivo é verificar se cada unidade de funcionalidade tem o desempenho esperado, para que os erros não se propaguem pelo aplicativo. Detetar um bug onde ele ocorre é mais eficiente do que observar o efeito de um bug indiretamente em um ponto secundário de falha.
O teste de unidade tem o efeito mais significativo na qualidade do código quando é parte integrante do fluxo de trabalho de desenvolvimento de software. Os testes de unidade podem atuar como documentação de projeto e especificações funcionais para uma aplicação. Assim que um método tiver sido escrito, testes de unidade devem ser escritos para verificar o comportamento do método em resposta a casos de dados de entrada padrão, limite e incorretos e verificar quaisquer suposições explícitas ou implícitas feitas pelo código. Alternativamente, com o desenvolvimento orientado a testes, os testes de unidade são escritos antes do código. Para obter mais informações sobre o desenvolvimento controlado por teste e como implementá-lo, consulte Passo a passo: desenvolvimento controlado por teste usando o Test Explorer..
Nota
Os testes unitários são muito eficazes contra a regressão. Ou seja, funcionalidade que costumava funcionar, mas foi perturbada por uma atualização defeituosa.
Os testes de unidade normalmente usam o padrão arrange-act-assert:
| Passo | Descrição |
|---|---|
| Dispor | Inicializa objetos e define o valor dos dados que são passados para o método em teste. |
| Ato | Invoca o método em teste com os argumentos necessários. |
| Asserção | Verifica se a ação do método em teste se comporta conforme o esperado. |
Esse padrão garante que os testes de unidade sejam legíveis, autodescritos e consistentes.
Injeção de dependência e testes unitários
Uma das motivações para a adoção de uma arquitetura de acoplamento flexível é que ela facilita o teste de unidade. Um dos tipos registrados com o serviço de injeção de dependência é a IAppEnvironmentService interface. O exemplo de código a seguir mostra um esboço dessa classe:
public class OrderDetailViewModel : ViewModelBase
{
private IAppEnvironmentService _appEnvironmentService;
public OrderDetailViewModel(
IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
: base(dialogService, navigationService, settingsService)
{
_appEnvironmentService = appEnvironmentService;
}
}
A OrderDetailViewModel classe tem uma dependência do IAppEnvironmentService tipo, que o contêiner de injeção de dependência resolve quando instancia um OrderDetailViewModel objeto. No entanto, em vez de criar um IAppEnvironmentService objeto que utiliza servidores, dispositivos e configurações reais para testar a OrderDetailViewModel classe de unidade, em vez disso, substitua o IAppEnvironmentService objeto por um objeto fictício para a finalidade dos testes. Um objeto simulado é aquele que tem a mesma assinatura de um objeto ou de uma interface, mas é criado de uma maneira específica para ajudar no teste de unidade. É frequentemente usado com injeção de dependência para fornecer implementações específicas de interfaces para testar diferentes cenários de dados e fluxo de trabalho.
Essa abordagem permite que o IAppEnvironmentService objeto seja passado para a OrderDetailViewModel classe em tempo de execução e, no interesse da estabilidade, permite que uma classe simulada seja passada para a OrderDetailViewModel classe no momento do teste. A principal vantagem dessa abordagem é que ela permite que testes de unidade sejam executados sem exigir recursos pesados, como recursos de plataforma de tempo de execução, serviços Web ou bancos de dados.
Testando aplicativos MVVM
Testar modelos e modelos de visualização de aplicativos MVVM é idêntico ao teste de qualquer outra classe e usa as mesmas ferramentas e técnicas; isto inclui funcionalidades como testes de unidade e criação de mocks. No entanto, alguns padrões que são típicos para modelar e exibir classes de modelo podem se beneficiar de técnicas específicas de teste de unidade.
Gorjeta
Em cada teste de unidade, teste uma coisa. À medida que a complexidade de um teste se expande, torna-se a verificação desse teste mais difícil. Ao limitar um teste de unidade a uma única preocupação, podemos garantir que nossos testes sejam mais repetíveis, isolados e tenham um tempo de execução menor. Consulte Práticas recomendadas de teste de unidade com .NET para obter mais práticas recomendadas.
Não se sinta tentado a fazer um teste de unidade exercitar mais do que um aspeto do comportamento da unidade. Isso leva a testes difíceis de ler e atualizar. Também pode gerar confusão ao interpretar uma falha.
A aplicação multiplataforma eShop utiliza o MSTest para realizar testes de unidade, que suportam dois tipos diferentes de testes de unidade:
| Tipo de teste | Atributo | Descrição |
|---|---|---|
| Método de Teste | TestMethod |
Define o método de teste real a ser executado. |
| Fonte de Dados | DataSource |
Testes que só são verdadeiros para um determinado conjunto de dados. |
Os testes unitários incluídos com a aplicação multi-plataforma eShop são TestMethod, por isso, cada método de teste unitário é decorado com o atributo TestMethod. Além do MSTest, existem várias outras estruturas de teste disponíveis, incluindo NUnit e xUnit.
Testando a funcionalidade assíncrona
Ao implementar o padrão MVVM, os modelos de exibição geralmente invocam operações em serviços, geralmente de forma assíncrona. Os testes de código que invoca essas operações normalmente usam simulações como substitutos para os serviços reais. O exemplo de código a seguir demonstra o teste de funcionalidade assíncrona passando um serviço fictício para um modelo de exibição:
[TestMethod]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
// Arrange
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
// Act
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
// Assert
Assert.IsNotNull(orderViewModel.Order);
}
Este teste de unidade verifica se a Order propriedade da OrderDetailViewModel instância terá um valor após o InitializeAsync método ter sido invocado. O método InitializeAsync é invocado quando se navega para a visão correspondente do modelo de visão. Para obter mais informações sobre navegação, consulte Navegação.
Quando a OrderDetailViewModel instância é criada, ela espera que uma IOrderService instância seja especificada como um argumento. No entanto, o OrderService recupera dados de um serviço Web. Portanto, uma OrderMockService instância, uma versão simulada da OrderService classe, é especificada como o argumento para o OrderDetailViewModel construtor. Em seguida, quando o método InitializeAsync do modelo de exibição é invocado, são recuperados dados fictícios, em vez de se comunicar com um serviço web, usando operações IOrderService.
Testando as implementações de INotifyPropertyChanged
A implementação da interface INotifyPropertyChanged permite que as visualizações reajam a alterações originadas de modelos de exibição e modelos. Essas alterações não se limitam aos dados mostrados nos controles -- elas também são usadas para controlar a exibição, como estados do modelo de exibição que fazem com que animações sejam iniciadas ou controles desabilitados.
As propriedades que podem ser atualizadas diretamente pelo teste de unidade podem ser testadas anexando um manipulador de eventos ao PropertyChanged evento e verificando se o evento é gerado depois de definir um novo valor para a propriedade. O exemplo de código a seguir mostra esse teste:
[TestMethod]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
var invoked = false;
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
orderViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Order"))
invoked = true;
};
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.IsTrue(invoked);
}
Este teste de unidade invoca o método InitializeAsync da classe OrderViewModel, o que faz com que a propriedade Order seja atualizada. O teste de unidade será aprovado, desde que o evento seja disparado para a propriedade PropertyChanged.
Testando a comunicação baseada em mensagens
Os modelos de exibição que usam a MessagingCenter classe para se comunicar entre classes com acoplamento flexível podem ser testados em unidade assinando a mensagem que está sendo enviada pelo código em teste, conforme demonstrado no exemplo de código a seguir:
[TestMethod]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
var messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.IsTrue(messageReceived);
}
Este teste de unidade verifica se o CatalogViewModel publica a mensagem AddProduct em resposta à execução do AddCatalogItemCommand. Como a MessagingCenter classe oferece suporte a subscrições de mensagens multicast, o teste de unidade pode subscrever a AddProduct mensagem e executar um delegado de callback em resposta a recebê-la. Esse delegado de retorno de chamada, especificado como uma expressão lambda, define um campo booleano que é usado pela Assert declaração para verificar o comportamento do teste.
Testando o tratamento de exceções
Os testes de unidade também podem ser escritos para verificar se exceções específicas são lançadas para ações ou entradas inválidas, conforme demonstrado no exemplo de código a seguir:
[TestMethod]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
Este teste de unidade lançará uma exceção porque o ListView controle não tem um evento chamado OnItemTapped. O Assert.Throws<T> método é um método genérico onde T é o tipo da exceção esperada. O argumento passado para o Assert.Throws<T> método é uma expressão lambda que lançará a exceção. Portanto, o teste de unidade será aprovado desde que a expressão lambda lance um ArgumentException.
Gorjeta
Evite escrever testes de unidade que examinem cadeias de caracteres de mensagem de exceção. As cadeias de caracteres de mensagens de exceção podem mudar ao longo do tempo e, portanto, os testes de unidade que dependem de sua presença são considerados frágeis.
Validação de testes
Há dois aspetos para testar a implementação de validação: testar se todas as regras de validação estão implementadas corretamente e testar se a ValidatableObject<T> classe executa conforme o esperado.
A lógica de validação é geralmente simples de testar, porque é tipicamente um processo autónomo em que a saída depende da entrada. Deve haver testes nos resultados da invocação do Validate método em cada propriedade que tenha pelo menos uma regra de validação associada, conforme demonstrado no exemplo de código a seguir:
[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
var isValid = mockViewModel.Validate();
Assert.IsTrue(isValid);
}
Este teste de unidade verifica se a validação é bem-sucedida quando ambas as propriedades ValidatableObject<T> na instância MockViewModel têm dados.
Além de verificar se a validação é bem-sucedida, os testes de unidade de validação também devem verificar os valores das propriedades Value, IsValid e Errors de cada instância de ValidatableObject<T>, para confirmar que a classe tem o desempenho esperado. O exemplo de código a seguir demonstra um teste de unidade que faz isso:
[TestMethod]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.IsFalse(isValid);
Assert.IsNotNull(mockViewModel.Forename.Value);
Assert.IsNull(mockViewModel.Surname.Value);
Assert.IsTrue(mockViewModel.Forename.IsValid);
Assert.IsFalse(mockViewModel.Surname.IsValid);
Assert.AreEqual(mockViewModel.Forename.Errors.Count(), 0);
Assert.AreNotEqual(mockViewModel.Surname.Errors.Count(), 0);
}
Este teste de unidade verifica que a validação falha quando a propriedade Surname de MockViewModel não tem dados, e que as propriedades Value, IsValid e Errors de cada instância de ValidatableObject<T> estão definidas corretamente.
Resumo
Um teste de unidade usa uma pequena unidade do aplicativo, normalmente um método, isola-a do restante do código e verifica se ela se comporta conforme o esperado. Seu objetivo é verificar se cada unidade de funcionalidade tem o desempenho esperado, para que os erros não se propaguem pelo aplicativo.
O comportamento de um objeto em teste pode ser isolado substituindo objetos dependentes por objetos fictícios que simulam o comportamento dos objetos dependentes. Isso permite que os testes de unidade sejam executados sem a necessidade de recursos pesados, como recursos de plataforma de tempo de execução, serviços Web ou bancos de dados
O teste de modelos e modelos de exibição de aplicativos MVVM é idêntico ao teste de quaisquer outras classes, e as mesmas ferramentas e técnicas podem ser usadas.