Partilhar via


Adicionar testes de unidades

Agora que seus ViewModels e serviços estão em uma biblioteca de classes separada, você pode criar facilmente testes de unidade. Adicionar projetos de teste de unidade permite verificar se seus ViewModels e serviços se comportam conforme o esperado sem depender da camada de interface do usuário ou de testes manuais. Você pode executar testes de unidade automaticamente como parte do fluxo de trabalho de desenvolvimento, garantindo que seu código permaneça confiável e sustentável.

Criar um projeto de teste de unidade

  1. Clique com o botão direito do rato na solução no Solution Explorer.
  2. Selecione Adicionar>novo projeto....
  3. Escolha o modelo WinUI Unit Test App e selecione Next.
  4. Nomeie o projeto WinUINotes.Tests e selecione Criar.

Adicionar referências do projeto

  1. Clique com o botão direito do mouse no projeto WinUINotes.Tests e selecione Adicionar>referência do projeto....
  2. Verifique o projeto WinUINotes.Bus e selecione OK.

Crie implementações falsas para testes

Para testes, crie implementações falsas do serviço de arquivos e classes de armazenamento que realmente não gravam no disco. Fakes são implementações leves que simulam o comportamento de dependências reais para fins de teste.

  1. No projeto WinUINotes.Tests , crie uma nova pasta chamada Fakes.

  2. Adicione um arquivo FakeFileService.cs de classe na pasta Fakes:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Windows.Storage;
    using WinUINotes.Services;
    
    namespace WinUINotes.Tests.Fakes
    {
        internal class FakeFileService : IFileService
        {
            private Dictionary<string, string> fileStorage = [];
    
            public async Task CreateOrUpdateFileAsync(string filename, string contents)
            {
                if (fileStorage.ContainsKey(filename))
                {
                    fileStorage[filename] = contents;
                }
                else
                {
                    fileStorage.Add(filename, contents);
                }
    
                await Task.Delay(10); // Simulate some async work
            }
    
            public async Task DeleteFileAsync(string filename)
            {
                if (fileStorage.ContainsKey(filename))
                {
                    fileStorage.Remove(filename);
                }
    
                await Task.Delay(10); // Simulate some async work
            }
    
            public bool FileExists(string filename)
            {
                if (string.IsNullOrEmpty(filename))
                {
                    throw new ArgumentException("Filename cannot be null or empty", nameof(filename));
                }
    
                if (fileStorage.ContainsKey(filename))
                {
                    return true;
                }
    
                return false;
            }
    
            public IStorageFolder GetLocalFolder()
            {
                return new FakeStorageFolder(fileStorage);
            }
    
            public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync()
            {
                await Task.Delay(10);
                return GetStorageItemsInternal();
            }
    
            public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(IStorageFolder storageFolder)
            {
                await Task.Delay(10);
                return GetStorageItemsInternal();
            }
    
            private IReadOnlyList<IStorageItem> GetStorageItemsInternal()
            {
                return fileStorage.Keys.Select(filename => CreateFakeStorageItem(filename)).ToList();
            }
    
            private IStorageItem CreateFakeStorageItem(string filename)
            {
                return new FakeStorageFile(filename);
            }
    
            public async Task<string> GetTextFromFileAsync(IStorageFile file)
            {
                await Task.Delay(10);
    
                if (fileStorage.ContainsKey(file.Name))
                {
                    return fileStorage[file.Name];
                }
    
                return string.Empty;
            }
        }
    }
    

    O FakeFileService usa um dicionário na memória (fileStorage) para simular operações de arquivo sem tocar no sistema de arquivos real. As principais funcionalidades incluem:

    • Simulação assíncrona: usa Task.Delay(10) para imitar operações de arquivo assíncronas reais
    • Validação: lança exceções para entradas inválidas, assim como a implementação real
    • Integração com classes de armazenamento falsas: retorna FakeStorageFolder e FakeStorageFile instâncias que trabalham juntas para simular a API de armazenamento do Windows
  3. Adicionar FakeStorageFolder.cs:

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices.WindowsRuntime;
    using Windows.Foundation;
    using Windows.Storage;
    using Windows.Storage.FileProperties;
    using Windows.Storage.Search;
    
    namespace WinUINotes.Tests.Fakes
    {
        internal class FakeStorageFolder : IStorageFolder
        {
            private string name;
            private Dictionary<string, string> fileStorage = [];
    
            public FakeStorageFolder(Dictionary<string, string> files)
            {
                fileStorage = files;
            }
    
            public FileAttributes Attributes => throw new NotImplementedException();
            public DateTimeOffset DateCreated => throw new NotImplementedException();
            public string Name => name;
            public string Path => throw new NotImplementedException();
    
            public IAsyncOperation<StorageFile> CreateFileAsync(string desiredName)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFile> CreateFileAsync(string desiredName, CreationCollisionOption options)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFolder> CreateFolderAsync(string desiredName)
            {
                throw new NotImplementedException();
            }
    
            // Only partial implementation shown for brevity
            ...
        }
    }
    

    O FakeStorageFolder usa o dicionário de armazenamento de arquivos no seu construtor, permitindo que ele trabalhe com o mesmo sistema de arquivos na memória que FakeFileService. A maioria dos membros da interface lança, NotImplementedException uma vez que apenas as propriedades e métodos realmente usados pelos testes precisam ser implementados.

    Você pode visualizar a implementação completa de FakeStorageFolder no repositório de código do GitHub para este tutorial.

  4. Adicionar FakeStorageFile.cs:

    using System;
    using System.IO;
    using System.Runtime.InteropServices.WindowsRuntime;
    using Windows.Foundation;
    using Windows.Storage;
    using Windows.Storage.FileProperties;
    using Windows.Storage.Streams;
    
    namespace WinUINotes.Tests.Fakes
    {
        public class FakeStorageFile : IStorageFile
        {
            private string name;
    
            public FakeStorageFile(string name)
            {
                this.name = name;
            }
    
            public string ContentType => throw new NotImplementedException();
            public string FileType => throw new NotImplementedException();
            public FileAttributes Attributes => throw new NotImplementedException();
            public DateTimeOffset DateCreated => throw new NotImplementedException();
            public string Name => name;
            public string Path => throw new NotImplementedException();
    
            public IAsyncOperation<StorageFile> CopyAsync(IStorageFolder destinationFolder)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace)
            {
                throw new NotImplementedException();
            }
    
            // Only partial implementation shown for brevity
            ...
        }
    }
    

    O FakeStorageFile representa arquivos individuais no sistema de armazenamento falso. Ele armazena o nome do arquivo e fornece a implementação mínima necessária para os testes. Como FakeStorageFolder, ele só implementa os membros que são realmente usados pelo código que está sendo testado.

    Você pode visualizar a implementação completa de FakeStorageFolder no repositório de código do GitHub para este tutorial.

Saiba mais nos documentos:

Escrever um teste de unidade simples

  1. Renomeie UnitTest1.cs para NoteTests.cs e atualize-o:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using WinUINotes.Tests.Fakes;
    
    namespace WinUINotes.Tests
    {
        [TestClass]
        public partial class NoteTests
        {
            [TestMethod]
            public void TestCreateUnsavedNote()
            {
                var noteVm = new ViewModels.NoteViewModel(new FakeFileService());
                Assert.IsNotNull(noteVm);
                Assert.IsTrue(noteVm.Date > DateTime.Now.AddHours(-1));
                Assert.IsTrue(noteVm.Filename.EndsWith(".txt"));
                Assert.IsTrue(noteVm.Filename.StartsWith("notes"));
                noteVm.Text = "Sample Note";
                Assert.AreEqual("Sample Note", noteVm.Text);
                noteVm.SaveCommand.Execute(null);
                Assert.AreEqual("Sample Note", noteVm.Text);
            }
        }
    }
    

    Este teste mostra como testar a unidade usando o NoteViewModelFakeFileService. O teste cria um novo NoteViewModel, verifica seu estado inicial (a data é recente, o nome do arquivo segue o padrão esperado), define o texto na nota, executa o comando salvar e confirma que o texto persiste. Como o serviço de arquivos falso é usado em vez da implementação real, o teste é executado rapidamente sem qualquer E/S de arquivo real e pode ser executado repetidamente sem efeitos colaterais.

Saiba mais nos documentos:

Executar os testes

  1. Abra a janela Test Explorer no Visual Studio (Test>Test Explorer).
  2. Selecione Executar todos os testes para executar o teste de unidade.
  3. Verifique se o teste foi aprovado.

Agora você tem uma arquitetura testável onde pode testar seus ViewModels e serviços independentemente da interface do usuário!

Resumo

Nesta série de tutoriais, você aprendeu como:

  • Crie um projeto de biblioteca de classes separado (projeto Bus) para armazenar seus ViewModels e serviços, permitindo o teste de unidade separado da camada da interface do usuário.
  • Implemente o padrão MVVM usando o MVVM Toolkit, aproveitando ObservableObject, [ObservableProperty] atributos e [RelayCommand] para reduzir o código repetitivo.
  • Use geradores de código-fonte para criar automaticamente notificações de alteração de propriedade e implementações de comando.
  • Use [NotifyCanExecuteChangedFor] para atualizar automaticamente a disponibilidade do comando quando os valores de propriedade forem alterados.
  • Integre a injeção de dependência usando Microsoft.Extensions.DependencyInjection para gerenciar o ciclo de vida de ViewModels e serviços.
  • Crie uma interface e implementação IFileService para lidar com operações de arquivo de forma testável.
  • Configurar o contêiner DI em App.xaml.cs e recuperar os ViewModels do provedor de serviços nas suas páginas.
  • Implemente o WeakReferenceMessenger para permitir o acoplamento solto entre componentes, permitindo que as páginas respondam a eventos do ViewModel sem referências diretas.
  • Crie classes de mensagem que herdem de ValueChangedMessage<T> para transportar dados entre componentes.
  • Crie implementações falsas de dependências para teste sem tocar no sistema de arquivos real.
  • Escreva testes de unidade usando MSTest para verificar o comportamento ViewModel independentemente da camada da interface do usuário.

Essa arquitetura fornece uma base sólida para a criação de aplicativos WinUI fáceis de manter e testáveis, com separação clara de preocupações entre a interface do usuário, a lógica de negócios e as camadas de acesso a dados. Você pode baixar ou visualizar o código para este tutorial no repositório GitHub.

Próximos passos

Agora que você entende como implementar o MVVM com o MVVM Toolkit e a injeção de dependência, você pode explorar tópicos mais avançados:

  • Mensagens avançadas: explore padrões de mensagens adicionais, incluindo mensagens de solicitação/resposta e tokens de mensagens para tratamento seletivo de mensagens.
  • Validação: adicione a validação de entrada aos seus ViewModels usando anotações de dados e os recursos de validação do MVVM Toolkit.
  • Comandos assíncronos: saiba mais sobre a execução assíncrona de comandos, suporte a cancelamento e relatórios de progresso com AsyncRelayCommando .
  • Testes avançados: explore cenários de teste mais avançados, incluindo o tratamento de mensagens de teste, a execução assíncrona de comandos e notificações de alteração de propriedade.
  • Coleções observáveis: use ObservableCollection<T> de forma eficaz e explore ObservableRangeCollection<T> para operações em massa.