ViewModels 및 서비스가 별도의 클래스 라이브러리에 있으므로 단위 테스트를 쉽게 만들 수 있습니다. 단위 테스트 프로젝트를 추가하면 UI 계층 또는 수동 테스트에 의존하지 않고 ViewModels 및 서비스가 예상대로 작동하는지 확인할 수 있습니다. 개발 워크플로의 일부로 단위 테스트를 자동으로 실행하여 코드가 안정적이고 유지 관리 가능한 상태로 유지되도록 할 수 있습니다.
단위 테스트 프로젝트 만들기
- 솔루션 탐색기에서 솔루션을 마우스 오른쪽 단추로 클릭합니다.
- 새 프로젝트>...를 선택합니다.
- WinUI 단위 테스트 앱 템플릿을 선택하고 다음을 선택합니다.
- 프로젝트
WinUINotes.Tests이름을 지정하고 선택한 다음,만들기를 선택합니다.
프로젝트 참조 추가
- WinUINotes.Tests 프로젝트를 마우스 오른쪽 단추로 클릭하고프로젝트 참조>...를 선택합니다.
- WinUINotes.Bus 프로젝트를 선택하고 확인을 선택합니다.
테스트를 위한 가짜 구현 만들기
테스트를 위해 실제로 디스크에 쓰지 않는 파일 서비스 및 스토리지 클래스의 가짜 구현을 만듭니다. Fakes는 테스트를 위해 실제 종속성의 동작을 시뮬레이션하는 간단한 구현입니다.
WinUINotes.Tests 프로젝트에서 Fakes라는 새 폴더를 만듭니다.
Fakes 폴더에 클래스 파일을
FakeFileService.cs추가합니다.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내 사전(fileStorage)을 사용하여 실제 파일 시스템을 건드리지 않고 파일 작업을 시뮬레이션합니다. 주요 기능은 다음과 같습니다.-
비동기 시뮬레이션: 실제 비동기 파일 작업을 모방하는 데 사용
Task.Delay(10) - 유효성 검사: 실제 구현과 마찬가지로 잘못된 입력에 대한 예외를 throw합니다.
-
Fake Storage 클래스와의 통합: Windows Storage API를 시뮬레이션하기 위해 함께 작동하는
FakeStorageFolder및FakeStorageFile인스턴스를 반환합니다.
-
비동기 시뮬레이션: 실제 비동기 파일 작업을 모방하는 데 사용
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생성자에서 가져와서 메모리 내 파일 시스템과FakeFileService동일한 방식으로 작업할 수 있습니다. 대부분의 인터페이스 멤버는 테스트에서 실제로 사용되는 속성과 메서드만 구현해야 하므로 throwNotImplementedException합니다.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스토리지 시스템의 개별 파일을 나타냅니다. 파일 이름을 저장하고 테스트에 필요한 최소한의 구현을 제공합니다. 마찬가지로FakeStorageFolder테스트 중인 코드에서 실제로 사용되는 멤버만 구현합니다.
다음 문서에서 자세히 알아보세요.
간단한 단위 테스트 작성
UnitTest1.cs이름을 바꾸고NoteTests.cs업데이트합니다.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); } } }이 테스트는
FakeFileService을(를) 사용하여NoteViewModel을(를) 단위 테스트하는 방법을 보여줍니다. 테스트는 새NoteViewModel항목을 만들고, 초기 상태를 확인하고(날짜가 최근이고, 파일 이름이 예상된 패턴을 따르고, 메모에 텍스트를 설정하고, 저장 명령을 실행하고, 텍스트가 지속되는지 확인합니다. 가짜 파일 서비스는 실제 구현 대신 사용되므로 테스트는 실제 파일 I/O 없이 빠르게 실행되며 부작용 없이 반복적으로 실행할 수 있습니다.
다음 문서에서 자세히 알아보세요.
테스트 실행
- Visual Studio에서 테스트 탐색기 창을 엽니다(테스트>탐색기).
- 모든 테스트 실행을 선택하여 단위 테스트를 실행합니다.
- 테스트가 통과했는지 확인합니다.
이제 UI와 독립적으로 ViewModels 및 서비스를 테스트할 수 있는 테스트 가능한 아키텍처가 있습니다.
요약
이 자습서 시리즈에서는 다음 방법을 알아보았습니다.
- ViewModels 및 서비스를 저장할 별도의 클래스 라이브러리 프로젝트(Bus 프로젝트)를 만들어 UI 계층과 별도로 단위 테스트를 사용하도록 설정합니다.
- MVVM 도구 키트를 사용하여 MVVM 패턴을 구현하고,
ObservableObject와[ObservableProperty]특성을 활용하며[RelayCommand]를 사용하여 반복적인 코드를 줄입니다. - 원본 생성기를 사용하여 속성 변경 알림 및 명령 구현을 자동으로 만듭니다.
- 속성 값이 변경되면 명령 가용성을 자동으로 업데이트하는 데 사용합니다
[NotifyCanExecuteChangedFor]. - 종속성 주입 기능인
Microsoft.Extensions.DependencyInjection를 사용하여 ViewModels 및 서비스의 수명 주기를 관리합니다. -
IFileService테스트 가능한 방식으로 파일 작업을 처리하는 인터페이스 및 구현을 만듭니다. - DI 컨테이너
App.xaml.cs를 구성하고 페이지의 서비스 프로바이더에서 ViewModels을 검색합니다. -
WeakReferenceMessenger구성 요소 간에 느슨한 결합을 사용하도록 설정하여 페이지가 직접 참조 없이 ViewModel 이벤트에 응답할 수 있도록 구현합니다. - 구성 요소 간에 데이터를 전송하기 위해
ValueChangedMessage<T>를 상속하는 메시지 클래스를 만듭니다. - 실제 파일 시스템을 건드리지 않고 테스트용 종속성의 가짜 구현을 만듭니다.
- MSTest를 사용하여 단위 테스트를 작성하여 UI 계층과 독립적으로 ViewModel 동작을 확인합니다.
이 아키텍처는 UI, 비즈니스 논리 및 데이터 액세스 계층 간의 문제를 명확하게 분리하여 유지 관리 가능하고 테스트 가능한 WinUI 애플리케이션을 빌드하기 위한 견고한 기반을 제공합니다. GitHub 리포지토리에서 이 자습서의 코드를 다운로드하거나 볼 수 있습니다.
다음 단계
이제 MVVM 도구 키트 및 종속성 주입을 사용하여 MVVM을 구현하는 방법을 이해했으므로 고급 항목을 살펴볼 수 있습니다.
- 고급 메시징: 선택적 메시지 처리를 위한 요청/응답 메시지 및 메시지 토큰을 비롯한 추가 메시징 패턴을 탐색합니다.
- 유효성 검사: 데이터 주석 및 MVVM 도구 키트의 유효성 검사 기능을 사용하여 ViewModels에 입력 유효성 검사를 추가합니다.
-
비동기 명령: 비동기 명령 실행, 취소 지원 및 진행률 보고에 대해 자세히 알아봅니다
AsyncRelayCommand. - 고급 테스트: 메시지 처리 테스트, 비동기 명령 실행 및 속성 변경 알림을 비롯한 고급 테스트 시나리오를 살펴봅니다.
-
관찰 가능한 컬렉션:
ObservableCollection<T>를 효과적으로 사용하고, 대량 작업을 위해ObservableRangeCollection<T>를 탐색합니다.
관련 콘텐츠
Windows developer