Sdílet prostřednictvím


Přidání testů jednotek

Teď, když jsou vaše ViewModely a služby ve samostatné knihovně tříd, můžete snadno vytvářet jednotkové testy. Přidání projektů testů jednotek umožňuje ověřit, že se modely ViewModel a služby chovají podle očekávání, aniž by se museli spoléhat na vrstvu uživatelského rozhraní nebo ruční testování. Testy jednotek můžete spouštět automaticky jako součást vývojového pracovního postupu a zajistit tak, aby váš kód zůstal spolehlivý a udržovatelný.

Vytvoření projektu testování jednotek

  1. V Průzkumníku řešení klikněte pravým tlačítkem myši na řešení.
  2. Vyberte Přidat>nový projekt....
  3. Zvolte šablonu aplikace Pro testování jednotek WinUI a vyberte Další.
  4. Pojmenujte projekt WinUINotes.Tests a vyberte Vytvořit.

Přidání odkazů na projekt

  1. Klikněte pravým tlačítkem myši na projekt WinUINotes.Tests a vyberte Přidat>odkaz na projekt....
  2. Zkontrolujte projekt WinUINotes.Bus a vyberte OK.

Vytváření falešných implementací pro testování

Pro účely testování vytvořte falešné implementace souborové služby a tříd úložiště, které ve skutečnosti nezapisují na disk. Fakes jsou jednoduché implementace, které simulují chování skutečných závislostí pro účely testování.

  1. V projektu WinUINotes.Tests vytvořte novou složku s názvem Fakes.

  2. Přidejte soubor FakeFileService.cs třídy do složky 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;
            }
        }
    }
    

    Používá FakeFileService slovník v paměti (fileStorage) k simulaci operací se soubory bez zásahu do skutečného systému souborů. Mezi klíčové funkce patří:

    • Asynchronní simulace: Používá Task.Delay(10) se k napodobení skutečných asynchronních operací se soubory.
    • Ověřování: Vyvolá výjimky pro neplatné vstupy, stejně jako skutečná implementace.
    • Integrace s falešnými třídami úložiště: Vrácení FakeStorageFolder a FakeStorageFile instance, které spolupracují na simulaci rozhraní API služby Windows Storage
  3. Přidat 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
            ...
        }
    }
    

    Konstruktor FakeStorageFolder přijímá slovník úložiště souborů, což mu umožňuje pracovat se stejným systémem souborů v paměti jako FakeFileService. Většina členů rozhraní vyvolá výjimku NotImplementedException, protože je potřeba implementovat pouze vlastnosti a metody, které testy skutečně používají.

    Pro účely tohoto kurzu si můžete prohlédnout úplnou implementaci FakeStorageFolder v úložišti kódu GitHubu .

  4. Přidat 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
            ...
        }
    }
    

    Objekt FakeStorageFile představuje jednotlivé soubory v systému falešného úložiště. Uloží název souboru a poskytuje minimální implementaci potřebnou pro testy. Podobně jako FakeStorageFolderimplementuje pouze členy, které skutečně používá testovaný kód.

    Pro účely tohoto kurzu si můžete prohlédnout úplnou implementaci FakeStorageFolder v úložišti kódu GitHubu .

Další informace najdete v dokumentaci:

Napište jednoduchý jednotkový test

  1. Přejmenujte UnitTest1.cs na NoteTests.cs a aktualizujte ho:

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

    Tento test ukazuje, jak pomocí FakeFileService testovat jednotku NoteViewModel. Test vytvoří nový NoteViewModel, zkontroluje jeho počáteční stav (datum je poslední, název souboru se řídí očekávaným vzorem), nastaví text na poznámce, spustí příkaz uložit a potvrdí, že text přetrvává. Vzhledem k tomu, že falešná souborová služba se používá místo skutečné implementace, test se spustí rychle bez skutečného vstupně-výstupního souboru a může běžet opakovaně bez vedlejších účinků.

Další informace najdete v dokumentaci:

Spuštění testů

  1. Otevřete okno Průzkumníka testů v sadě Visual Studio (Průzkumník testů>).
  2. Vyberte Spustit všechny testy a spusťte test jednotek.
  3. Ověřte, že test projde.

Teď máte testovatelnou architekturu, ve které můžete testovat modely ViewModel a služby nezávisle na uživatelském rozhraní.

Shrnutí

V této sérii kurzů jste se naučili:

  • Vytvořte samostatný projekt knihovny tříd (Bus project), který bude obsahovat ViewModely a služby, což umožňuje jednotkové testování odděleně od vrstvy uživatelského rozhraní.
  • Implementujte model MVVM pomocí MVVM Toolkit, využitím atributů ObservableObject, [ObservableProperty] a [RelayCommand] pro redukci nadbytečného kódu.
  • Pomocí generátorů zdrojů můžete automaticky vytvářet oznámení o změnách vlastností a implementace příkazů.
  • Slouží [NotifyCanExecuteChangedFor] k automatické aktualizaci dostupnosti příkazů při změně hodnot vlastností.
  • Integrujte injektáž závislostí pomocí Microsoft.Extensions.DependencyInjection pro řízení životního cyklu modelů „ViewModels“ a služeb.
  • IFileService Vytvořte rozhraní a implementaci pro zpracování operací se soubory testovatelným způsobem.
  • Nakonfigurujte kontejner DI v App.xaml.cs a načtěte ViewModely od poskytovatele služeb na stránkách.
  • WeakReferenceMessenger Implementujte povolení volné vazby mezi součástmi, což umožňuje stránkám reagovat na události ViewModel bez přímých odkazů.
  • Vytvořte třídy zpráv, které dědí z ValueChangedMessage<T>, aby přenášely data mezi komponentami.
  • Vytvářejte falešné implementace závislostí pro testování, aniž byste se museli dotýkat skutečného systému souborů.
  • Pište jednotkové testy pomocí MSTest pro ověření chování ViewModelu nezávisle na vrstvě uživatelského rozhraní.

Tato architektura poskytuje solidní základ pro vytváření udržovatelných, testovatelných aplikací WinUI s jasným oddělením obav mezi uživatelským rozhraním, obchodní logikou a vrstvami přístupu k datům. Kód pro účely tohoto kurzu si můžete stáhnout nebo zobrazit z úložiště GitHub.

Další kroky

Teď, když rozumíte tomu, jak implementovat MVVM pomocí MVVM Toolkit a injektáž závislostí, můžete prozkoumat pokročilejší témata:

  • Pokročilé zasílání zpráv: Prozkoumejte další vzory zasílání zpráv, včetně zpráv žádostí a odpovědí a tokenů zpráv pro selektivní zpracování zpráv.
  • Ověření: Přidejte do modelů ViewModel vstupní ověřování pomocí datových poznámek a ověřovacích funkcí MVVM Toolkit.
  • Asynchronní příkazy: Přečtěte si další informace o asynchronním spouštění příkazů, podpoře zrušení a vykazování průběhu pomocí AsyncRelayCommand.
  • Rozšířené testování: Prozkoumejte pokročilejší scénáře testování, včetně testování zpracování zpráv, asynchronního spouštění příkazů a oznámení o změnách vlastností.
  • Pozorovatelné kolekce: Efektivně používejte ObservableCollection<T> a prozkoumejte ObservableRangeCollection<T> pro hromadné operace.