Teste de unidades
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 | Description |
---|---|
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 visualizar modelos de aplicativos MVVM é idêntico ao teste de qualquer outra classe e usa as mesmas ferramentas e técnicas; Isso inclui recursos como testes de unidade e simulação. 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
Teste uma coisa com cada teste de unidade. À 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 | Description |
---|---|---|
TestMethod | TestMethod |
Define o método de teste real a ser executado. |
DataSource | DataSource |
Testes que só são verdadeiros para um determinado conjunto de dados. |
Os testes de unidade incluídos com o aplicativo multiplataforma eShop são TestMethod, portanto, cada método de teste de unidade é decorado com o TestMethod
atributo. 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 InitializeAsync
método é invocado quando a exibição correspondente do modelo de exibição é navegada. 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, os dados fictícios InitializeAsync
são recuperados em vez de se comunicar com um serviço Web quando o método do modelo de exibição é invocado, que usa IOrderService
operações.
Testando implementações INotifyPropertyChanged
A implementação da interface permite que as INotifyPropertyChanged
visualizações reajam a alterações originadas de modelos e modelos de exibição. 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 InitializeAsync
método da classe, o OrderViewModel
que faz com que sua Order
propriedade seja atualizada. O teste de unidade será aprovado, desde que o evento seja levantado PropertyChanged
para o Order
imóvel.
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 publica CatalogViewModel
a AddProduct
mensagem em resposta à sua AddCatalogItemCommand
execução. Como a MessagingCenter
classe oferece suporte a assinaturas de mensagens multicast, o teste de unidade pode assinar a AddProduct
mensagem e executar um delegado de retorno de chamada 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
instruçã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
arquivo .
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 as duas ValidatableObject<T>
propriedades na MockViewModel
instância 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 Value
valores da propriedade , IsValid
e Errors
de cada ValidatableObject<T>
instância, para verificar se 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 se a MockViewModel
validação falha quando a Surname
propriedade do não tem dados e a propriedade , IsValid
e Errors
de Value
cada ValidatableObject<T>
instância está definida 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.