Compartilhar via


Adicionar testes de unidade

Agora que seus ViewModels e serviços estão em uma biblioteca de classes separada, você pode facilmente criar testes de unidade. A adição de 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 do teste manual. 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 mantenedível.

Criar um projeto de teste de unidade

  1. Clique com o botão direito do mouse na solução no Gerenciador de Soluções.
  2. Selecione Adicionar>Novo Projeto....
  3. Escolha o modelo de Aplicativo de Teste de Unidade do WinUI e selecione Avançar.
  4. Nomeie o projeto WinUINotes.Tests e selecione Criar.

Adicionar referências de 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.

Criar implementações falsas para teste

Para testar, crie implementações falsas do serviço de arquivos e das classes de armazenamento que realmente não gravam em disco. As falsificações 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 de classe FakeFileService.cs 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. Os principais recursos incluem:

    • Simulação assíncrona: usa Task.Delay(10) para imitar operações reais de arquivo assíncrono
    • Validação: gera 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 funcionam em conjunto 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
            ...
        }
    }
    

    FakeStorageFolder recebe o dicionário de armazenamento de arquivos em seu construtor, permitindo que funcione com o mesmo sistema de arquivos em memória que FakeFileService. A maioria dos membros da interface lança NotImplementedException, pois apenas as propriedades e os métodos realmente usados pelos testes precisam ser implementados.

    Você pode ver 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. Assim como FakeStorageFolder, ele implementa apenas os membros que são realmente usados pelo código que está sendo testado.

    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-o UnitTest1.csNoteTests.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 fazer o teste unitário do NoteViewModel usando o FakeFileService. 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 save e confirma se o texto persiste. Como o serviço de arquivo falso é usado em vez da implementação real, o teste é executado rapidamente sem nenhuma E/S de arquivo real e pode ser executado repetidamente sem efeitos colaterais.

Saiba mais nos documentos:

Executar os testes

  1. Abra a janela do Gerenciador de Testes no Visual Studio (Gerenciador de Testes>).
  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 em que pode testar seus ViewModels e serviços independentemente da interface do usuário!

Resumo

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

  • Crie um projeto de biblioteca de classes separado (projeto Bus) para armazenar seus ViewModels e serviços, permitindo que os testes de unidade sejam realizados de forma independente da camada de interface do usuário.
  • Implemente o padrão MVVM usando o Kit de Ferramentas MVVM, aproveitando ObservableObject, [ObservableProperty] atributos e [RelayCommand] reduzindo o código clichê.
  • Use geradores de origem 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 da 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 IFileService interface e implementação para lidar com operações de arquivo de maneira testável.
  • Configure o contêiner de DI em App.xaml.cs e recupere os ViewModels do provedor de serviços em suas páginas.
  • Implemente o WeakReferenceMessenger acoplamento flexível entre componentes, permitindo que as páginas respondam a eventos 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 do ViewModel de forma independente da camada de UI.

Essa arquitetura fornece uma base sólida para a criação de aplicativos WinUI compatíveis e testáveis com clara separação 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 exibir o código deste tutorial no repositório do GitHub.

Próximas etapas

Agora que você entende como implementar o MVVM com o Kit de Ferramentas MVVM 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 mensagem para tratamento seletivo de mensagens.
  • Validação: adicione validação de entrada aos ViewModels usando anotações de dados e os recursos de validação do Kit de Ferramentas do MVVM.
  • Comandos assíncronos: saiba mais sobre execução de comando assíncrono, suporte a cancelamento e relatórios de progresso com AsyncRelayCommand.
  • Teste Avançado: explore cenários de teste mais avançados, incluindo teste de tratamento de mensagens, execução de comando assíncrono e notificações de alteração de propriedade.
  • Coleções observáveis: use ObservableCollection<T> efetivamente e explore ObservableRangeCollection<T> para operações em massa.