Udostępnij przez


Dodawanie testów jednostkowych

Teraz, gdy modele i usługi ViewModels znajdują się w oddzielnej bibliotece klas, możesz łatwo tworzyć testy jednostkowe. Dodawanie projektów testów jednostkowych umożliwia sprawdzenie, czy modele i usługi ViewModels zachowują się zgodnie z oczekiwaniami bez polegania na warstwie interfejsu użytkownika lub testowaniu ręcznym. Testy jednostkowe można uruchamiać automatycznie w ramach przepływu pracy programowania, zapewniając, że kod pozostaje niezawodny i konserwowalny.

Tworzenie projektu testów jednostkowych

  1. Kliknij rozwiązanie prawym przyciskiem myszy w Eksploratorze rozwiązań.
  2. Wybierz pozycję Dodaj>nowy projekt....
  3. Wybierz szablon aplikacji WinUI Unit Test App i wybierz pozycję Dalej.
  4. Nadaj projektowi nazwę WinUINotes.Tests i wybierz pozycję Utwórz.

Dodawanie odwołań do projektu

  1. Kliknij prawym przyciskiem myszy projekt WinUINotes.Tests i wybierz polecenie Dodaj>Odwołanie do projektu....
  2. Sprawdź projekt WinUINotes.Bus i wybierz przycisk OK.

Tworzenie fałszywych implementacji na potrzeby testowania

Na potrzeby testowania utwórz fałszywe implementacje klas usługi plików i pamięci, które nie zapisują do systemu plików. Fałszywe to lekkie implementacje, które symulują zachowanie rzeczywistych zależności na potrzeby testowania.

  1. W projekcie WinUINotes.Tests utwórz nowy folder o nazwie Fakes.

  2. Dodaj plik FakeFileService.cs klasy w folderze 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;
            }
        }
    }
    

    Funkcja FakeFileService używa słownika w pamięci (fileStorage) do symulowania operacji na plikach bez dotykania rzeczywistego systemu plików. Najważniejsze funkcje:

    • Symulacja asynchroniczna: używa Task.Delay(10) do naśladowania rzeczywistych operacji plików asynchronicznych
    • Walidacja: zgłasza wyjątki dla nieprawidłowych danych wejściowych, podobnie jak rzeczywista implementacja
    • Integracja z fałszywymi klasami magazynu: zwraca wystąpienia FakeStorageFolder i FakeStorageFile, które pracują wspólnie nad symulacją interfejsu API Windows Storage.
  3. Dodaj FakeStorageFolder.cspolecenie :

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

    Obiekt FakeStorageFolder przyjmuje słownik przechowywania plików w swoim konstruktorze, co umożliwia pracę z tym samym systemem plików w pamięci, co FakeFileService. Większość elementów członkowskich interfejsu zgłasza NotImplementedException, ponieważ konieczne jest zaimplementowanie jedynie tych właściwości i metod, które są wykorzystywane przez testy.

    Możesz wyświetlić pełną implementację FakeStorageFolder w repozytorium kodu GitHub dla tego samouczka.

  4. Dodaj FakeStorageFile.cspolecenie :

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

    FakeStorageFile reprezentuje pojedyncze pliki w fałszywym systemie przechowywania danych. Przechowuje nazwę pliku i zapewnia minimalną implementację wymaganą do testów. Podobnie jak FakeStorageFolder, implementuje tylko te członki, które są rzeczywiście używane przez testowany kod.

    Możesz wyświetlić pełną implementację FakeStorageFolder w repozytorium kodu GitHub dla tego samouczka.

Dowiedz się więcej w dokumentacji:

Pisanie prostego testu jednostkowego

  1. Zmień nazwę UnitTest1.cs na NoteTests.cs i zaktualizuj ją:

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

    W tym teście pokazano, jak przeprowadzić test jednostkowy NoteViewModel przy użyciu elementu FakeFileService. Test tworzy nowy NoteViewModel element, sprawdza jego stan początkowy (data jest świeża, nazwa pliku jest zgodna z oczekiwanym wzorcem), ustawia tekst w notatce, uruchamia polecenie zapisywania i potwierdza, że tekst zostaje zachowany. Ponieważ fałszywa usługa plików jest używana zamiast rzeczywistej implementacji, test jest uruchamiany szybko bez rzeczywistego we/wy pliku i może działać wielokrotnie bez skutków ubocznych.

Dowiedz się więcej w dokumentacji:

Uruchamianie testów

  1. Otwórz okno Eksplorator testów w programie Visual Studio (Eksplorator testów>).
  2. Wybierz pozycję Uruchom wszystkie testy , aby wykonać test jednostkowy.
  3. Sprawdź, czy test przebiegnie pomyślnie.

Masz teraz architekturę testową, w której można testować modele i usługi niezależnie od interfejsu użytkownika!

Podsumowanie

W tej serii samouczków przedstawiono sposób wykonywania następujących instrukcji:

  • Utwórz oddzielny projekt biblioteki klas (projekt Bus) do przechowywania ViewModeli i usług, umożliwiając testowanie jednostkowe niezależnie od warstwy interfejsu użytkownika.
  • Zaimplementuj wzorzec MVVM przy użyciu zestawu narzędzi MVVM, wykorzystując ObservableObject atrybuty, [ObservableProperty] i [RelayCommand], aby zredukować nadmiarowy kod.
  • Użyj generatorów źródłowych, aby automatycznie tworzyć powiadomienia o zmianie właściwości i implementacje poleceń.
  • Użyj [NotifyCanExecuteChangedFor] polecenia , aby automatycznie aktualizować dostępność poleceń po zmianie wartości właściwości.
  • Zintegruj wstrzykiwanie zależności za pomocą Microsoft.Extensions.DependencyInjection, aby zarządzać cyklem życia ViewModeli i usług.
  • Tworzenie interfejsu IFileService i implementacji do obsługi operacji na plikach w możliwy do przetestowania sposób.
  • Skonfiguruj kontener DI w App.xaml.cs i pobierz ViewModel z dostawcy usług na swoich stronach.
  • Zaimplementuj element , WeakReferenceMessenger aby umożliwić luźne sprzężenie między składnikami, dzięki czemu strony mogą reagować na zdarzenia ViewModel bez bezpośrednich odwołań.
  • Utwórz klasy komunikatów dziedziczone z ValueChangedMessage<T> w celu przenoszenia danych między składnikami.
  • Twórz fałszywe implementacje zależności na potrzeby testowania bez dotykania rzeczywistego systemu plików.
  • Pisanie testów jednostkowych przy użyciu narzędzia MSTest w celu zweryfikowania zachowania modelu ViewModel niezależnie od warstwy interfejsu użytkownika.

Ta architektura zapewnia solidną podstawę do tworzenia stałych, testowalnych aplikacji WinUI z wyraźnym rozdzieleniem problemów między interfejsem użytkownika, logiką biznesową i warstwami dostępu do danych. Możesz pobrać lub wyświetlić kod tego samouczka z repozytorium GitHub.

Dalsze kroki

Teraz, gdy już wiesz, jak zaimplementować maszynę MVVM za pomocą zestawu narzędzi MVVM Toolkit i iniekcji zależności, możesz zapoznać się z bardziej zaawansowanymi tematami:

  • Zaawansowane obsługa komunikatów: eksplorowanie dodatkowych wzorców obsługi komunikatów, w tym komunikatów żądań/odpowiedzi i tokenów komunikatów na potrzeby selektywnej obsługi komunikatów.
  • Walidacja: dodaj walidację danych wejściowych do modelu ViewModels przy użyciu adnotacji danych i funkcji weryfikacji zestawu narzędzi MVVM Toolkit.
  • Polecenia asynchroniczne: dowiedz się więcej na temat asynchronicznego wykonywania poleceń, obsługi anulowania i raportowania postępu za pomocą polecenia AsyncRelayCommand.
  • Testowanie zaawansowane: eksplorowanie bardziej zaawansowanych scenariuszy testowania, w tym testowanie obsługi komunikatów, asynchroniczne wykonywanie poleceń i powiadomienia o zmianie właściwości.
  • Obserwowalne kolekcje: efektywne używanie ObservableCollection<T> oraz analiza ObservableRangeCollection<T> dla operacji zbiorczych.