Partilhar via


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.

Padrões de aplicativos corporativos usando a miniatura da capa do eBook .NET MAUI .

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

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 Valuevalores da propriedade , IsValide 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 , IsValide Errors de Valuecada 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.