Bagikan melalui


Tambahkan pengujian unit

Sekarang setelah ViewModels dan layanan Anda berada di pustaka kelas terpisah, Anda dapat dengan mudah membuat unit test. Menambahkan proyek pengujian unit memungkinkan Anda memverifikasi bahwa ViewModels dan layanan Anda bertingkah seperti yang diharapkan tanpa mengandalkan lapisan UI atau pengujian manual. Anda dapat menjalankan pengujian unit secara otomatis sebagai bagian dari alur kerja pengembangan Anda, memastikan bahwa kode Anda tetap dapat diandalkan dan dipertahankan.

Membuat proyek pengujian unit

  1. Klik kanan solusi di Penjelajah Solusi.
  2. Pilih Tambahkan>Proyek Baru....
  3. Pilih templat Aplikasi Uji Unit WinUI dan pilih Berikutnya.
  4. Beri nama WinUINotes.Tests proyek dan pilih Buat.

Menambahkan referensi proyek

  1. Klik kanan proyek WinUINotes.Tests dan pilih Tambahkan>Referensi Proyek....
  2. Periksa proyek WinUINotes.Bus dan pilih OK.

Membuat implementasi palsu untuk pengujian

Untuk pengujian, buat implementasi palsu dari layanan file dan kelas penyimpanan yang tidak benar-benar menulis ke disk. Palsu adalah implementasi ringan yang mensimulasikan perilaku dependensi nyata untuk tujuan pengujian.

  1. Di proyek WinUINotes.Tests , buat folder baru bernama Palsu.

  2. Tambahkan file kelas FakeFileService.cs di folder 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 menggunakan kamus dalam memori (fileStorage) untuk mensimulasikan operasi file tanpa menyentuh sistem file yang sebenarnya. Fitur utama meliputi:

    • Simulasi asinkron: Menggunakan Task.Delay(10) untuk meniru operasi file asinkron nyata
    • Validasi: Melemparkan pengecualian untuk input yang tidak valid, sama seperti implementasi nyata
    • Integrasi dengan kelas penyimpanan palsu: Mengembalikan instans FakeStorageFolder dan FakeStorageFile yang bekerja sama untuk mensimulasikan Windows Storage API
  3. Tambahkan 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 menerima kamus penyimpanan file pada konstruktornya, memungkinkan perangkat ini untuk bekerja dengan sistem file dalam memori yang sama seperti FakeFileService. Sebagian besar anggota antarmuka menghasilkan NotImplementedException, karena hanya properti dan metode yang benar-benar digunakan oleh pengujian yang perlu diimplementasikan.

    Anda dapat melihat implementasi FakeStorageFolder lengkap di repositori kode GitHub untuk tutorial ini.

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

    FakeStorageFile mewakili sebuah file individual dalam sistem penyimpanan palsu. Ini menyimpan nama file dan menyediakan implementasi minimal yang diperlukan untuk pengujian. Seperti FakeStorageFolder, itu hanya mengimplementasikan anggota yang benar-benar digunakan oleh kode yang sedang diuji.

    Anda dapat melihat implementasi FakeStorageFolder lengkap di repositori kode GitHub untuk tutorial ini.

Pelajari selengkapnya di dokumen:

Menulis pengujian unit sederhana

  1. Ganti nama UnitTest1.cs menjadi NoteTests.cs dan perbarui:

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

    Pengujian ini menunjukkan cara menguji unit NoteViewModel dengan FakeFileService. Pengujian membuat sebuah NoteViewModel baru, memeriksa kondisi awalnya (tanggal terbaru, nama file sesuai dengan pola yang diharapkan), mengisi teks pada catatan, menjalankan perintah simpan, dan memastikan teks tersimpan. Karena layanan file palsu digunakan alih-alih implementasi nyata, pengujian berjalan dengan cepat tanpa I/O file aktual dan dapat berjalan berulang kali tanpa efek samping.

Pelajari selengkapnya di dokumen:

Jalankan pengujian

  1. Buka jendela Test Explorer di Visual Studio (Test>Explorer).
  2. Pilih Jalankan Semua Pengujian untuk menjalankan pengujian unit Anda.
  3. Verifikasi bahwa pengujian lolos.

Anda sekarang memiliki arsitektur yang dapat diuji di mana Anda dapat menguji ViewModels dan layanan Anda secara independen dari UI!

Ringkasan

Dalam seri tutorial ini, Anda mempelajari cara:

  • Buat proyek pustaka kelas terpisah (proyek Bus) untuk menyimpan ViewModels dan layanan Anda, memungkinkan pengujian unit yang terpisah dari lapisan UI.
  • Terapkan pola MVVM menggunakan Toolkit MVVM, memanfaatkan ObservableObject, [ObservableProperty] atribut, dan [RelayCommand] untuk mengurangi kode boilerplate.
  • Gunakan generator sumber untuk membuat pemberitahuan perubahan properti dan implementasi perintah secara otomatis.
  • Gunakan [NotifyCanExecuteChangedFor] untuk memperbarui ketersediaan perintah secara otomatis saat nilai properti berubah.
  • Integrasikan injeksi dependensi menggunakan Microsoft.Extensions.DependencyInjection untuk mengelola siklus hidup ViewModels dan layanan.
  • IFileService Buat antarmuka dan implementasi untuk menangani operasi file dengan cara yang dapat diuji.
  • Konfigurasikan kontainer DI di App.xaml.cs dan ambil ViewModels dari penyedia layanan di halaman Anda.
  • Terapkan WeakReferenceMessenger untuk mengaktifkan kopling longgar antar komponen, memungkinkan halaman merespons peristiwa ViewModel tanpa referensi langsung.
  • Buat kelas pesan yang mewarisi dari ValueChangedMessage<T> untuk membawa data antar komponen.
  • Buat implementasi dependensi palsu untuk pengujian tanpa menyentuh sistem file yang sebenarnya.
  • Tulis pengujian unit menggunakan MSTest untuk memverifikasi perilaku ViewModel secara independen dari lapisan UI.

Arsitektur ini memberikan fondasi yang kuat untuk membangun aplikasi WinUI yang mudah dipelihara dan dapat diuji, dengan pemisahan tanggung jawab yang jelas antara UI, logika bisnis, dan lapisan akses data. Anda dapat mengunduh atau melihat kode untuk tutorial ini dari repositori GitHub.

Langkah selanjutnya

Sekarang setelah Anda memahami cara menerapkan MVVM dengan Toolkit MVVM dan injeksi dependensi, Anda dapat menjelajahi topik yang lebih canggih:

  • Pesan Tingkat Lanjut: Jelajahi pola olahpesan tambahan, termasuk pesan permintaan/respons dan token pesan untuk penanganan pesan selektif.
  • Validasi: Tambahkan validasi input ke ViewModels Anda menggunakan anotasi data dan fitur validasi MVVM Toolkit.
  • Perintah Asinkron: Pelajari selengkapnya tentang eksekusi perintah asinkron, dukungan pembatalan, dan pelaporan kemajuan dengan AsyncRelayCommand.
  • Pengujian Tingkat Lanjut: Jelajahi skenario pengujian yang lebih canggih, termasuk pengujian penanganan pesan, eksekusi perintah asinkron, dan pemberitahuan perubahan properti.
  • Koleksi yang Dapat Diamati: Gunakan ObservableCollection<T> secara efektif dan jelajahi ObservableRangeCollection<T> untuk operasi massal.