Dela via


Lägg till enhetstester

Nu när dina ViewModels och tjänster finns i ett separat klassbibliotek kan du enkelt skapa enhetstester. Genom att lägga till enhetstestprojekt kan du kontrollera att dina ViewModels och tjänster fungerar som förväntat utan att förlita sig på användargränssnittsskiktet eller manuell testning. Du kan köra enhetstester automatiskt som en del av ditt arbetsflöde för utveckling, vilket säkerställer att koden förblir tillförlitlig och underhållsbar.

Skapa ett enhetstestprojekt

  1. Högerklicka på lösningen i Solution Explorer.
  2. Välj Lägg till>nytt projekt....
  3. Välj mallen WinUI Unit Test App och välj Nästa.
  4. Ge projektet namnet WinUINotes.Tests och välj Skapa.

Lägga till projektreferenser

  1. Högerklicka på projektet WinUINotes.Tests och välj Lägg till>projektreferens....
  2. Kontrollera WinUINotes.Bus-projektet och välj OK.

Skapa falska implementeringar för testning

För testning skapar du falska implementeringar av filtjänst- och lagringsklasserna som faktiskt inte skriver till disk. Förfalskningar är enkla implementeringar som simulerar beteendet hos verkliga beroenden i testsyfte.

  1. I projektet WinUINotes.Tests skapar du en ny mapp med namnet Fakes.

  2. Lägg till en klassfil FakeFileService.cs i mappen 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;
            }
        }
    }
    

    FakeFileService Använder en minnesintern ordlista (fileStorage) för att simulera filåtgärder utan att röra det faktiska filsystemet. Viktiga funktioner är:

    • Async-simulering: Används Task.Delay(10) för att efterlikna verkliga asynkrona filåtgärder
    • Validering: Utlöser undantag för ogiltiga indata, precis som den verkliga implementeringen
    • Integrering med falska lagringsklasser: Returnerar FakeStorageFolder och FakeStorageFile instanser som fungerar tillsammans för att simulera Windows Storage-API:et
  3. Lägg till 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 tar fillagringsordlistan i konstruktorn så att den kan fungera med samma minnesinterna filsystem som FakeFileService. De flesta gränssnittsmedlemmar genererar NotImplementedException eftersom endast de egenskaper och metoder som faktiskt används av testerna måste implementeras.

    Du kan visa den fullständiga implementeringen av FakeStorageFolder i GitHub-kodförrådet för denna handledning.

  4. Lägg till 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
            ...
        }
    }
    

    Representerar FakeStorageFile enskilda filer i det falska lagringssystemet. Den lagrar filnamnet och ger den minimala implementering som krävs för testerna. Precis som FakeStorageFolderimplementerar den bara de medlemmar som faktiskt används av koden som testas.

    Du kan visa den fullständiga implementeringen av FakeStorageFolder i GitHub-kodlagringsplatsen för denna handledning.

Läs mer i dokumenten:

Skriva ett enkelt enhetstest

  1. Byt UnitTest1.cs namn på NoteTests.cs och uppdatera det:

    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);
            }
        }
    }
    

    Det här testet visar hur du enhetstestar NoteViewModel med hjälp FakeFileServiceav . Testet skapar ett nytt NoteViewModel, kontrollerar dess ursprungliga tillstånd (datumet är nyligen, filnamnet följer det förväntade mönstret), anger text på anteckningen, kör kommandot spara och bekräftar att texten bevaras. Eftersom den falska filtjänsten används i stället för den verkliga implementeringen körs testet snabbt utan någon faktisk fil-I/O och kan köras upprepade gånger utan biverkningar.

Läs mer i dokumenten:

Kör testerna

  1. Öppna testutforskarens fönster i Visual Studio (Test>Test Explorer).
  2. Välj Kör alla tester för att köra enhetstestet.
  3. Kontrollera att testet har godkänts.

Nu har du en testbar arkitektur där du kan testa dina ViewModels och tjänster oberoende av användargränssnittet!

Sammanfattning

I den här självstudieserien lärde du dig att:

  • Skapa ett separat klassbiblioteksprojekt (Bus-projekt) för att lagra dina ViewModels och tjänster, vilket möjliggör enhetstestning separat från användargränssnittsskiktet.
  • Implementera MVVM-mönstret med hjälp av MVVM Toolkit, utnyttja ObservableObject, [ObservableProperty] attribut och [RelayCommand] för att minska standardkoden.
  • Använd källgeneratorer för att automatiskt skapa meddelanden om egenskapsändring och kommandoimplementeringar.
  • Använd [NotifyCanExecuteChangedFor] för att automatiskt uppdatera kommandotillgängligheten när egenskapsvärdena ändras.
  • Integrera beroendeinmatning med hjälp av Microsoft.Extensions.DependencyInjection för att hantera livscykeln för ViewModels och tjänster.
  • Skapa ett IFileService gränssnitt och en implementering för att hantera filåtgärder på ett testbart sätt.
  • Konfigurera DI-containern i App.xaml.cs och hämta ViewModels från tjänstleverantören på dina sidor.
  • WeakReferenceMessenger Implementera för att aktivera lös koppling mellan komponenter så att sidorna kan svara på ViewModel-händelser utan direkta referenser.
  • Skapa meddelandeklasser som ärver från ValueChangedMessage<T> för att överföra data mellan komponenter.
  • Skapa falska implementeringar av beroenden för testning utan att röra det faktiska filsystemet.
  • Skriv enhetstester med MSTest för att verifiera ViewModel-beteendet oberoende av användargränssnittsskiktet.

Den här arkitekturen ger en solid grund för att skapa underhållsbara, testbara WinUI-program med tydlig uppdelning av problem mellan användargränssnittet, affärslogik och dataåtkomstskikt. Du kan ladda ned eller visa koden för den här självstudien från GitHub-lagringsplatsen.

Nästa steg

Nu när du förstår hur du implementerar MVVM med MVVM Toolkit och beroendeinmatning kan du utforska mer avancerade ämnen:

  • Avancerade meddelanden: Utforska ytterligare meddelandemönster, inklusive begärande-/svarsmeddelanden och meddelandetoken för selektiv meddelandehantering.
  • Validering: Lägg till indataverifiering i dina ViewModels med hjälp av dataanteckningar och valideringsfunktionerna för MVVM Toolkit.
  • Async-kommandon: Läs mer om asynkron kommandokörning, support för annullering och förloppsrapportering med AsyncRelayCommand.
  • Avancerad testning: Utforska mer avancerade testscenarier, inklusive testning av meddelandehantering, asynkron kommandokörning och meddelanden om egenskapsändring.
  • Observerbara samlingar: Använd ObservableCollection<T> effektivt och utforska ObservableRangeCollection<T> för storskaliga operationer.