다음을 통해 공유


단위 테스트 추가

ViewModels 및 서비스가 별도의 클래스 라이브러리에 있으므로 단위 테스트를 쉽게 만들 수 있습니다. 단위 테스트 프로젝트를 추가하면 UI 계층 또는 수동 테스트에 의존하지 않고 ViewModels 및 서비스가 예상대로 작동하는지 확인할 수 있습니다. 개발 워크플로의 일부로 단위 테스트를 자동으로 실행하여 코드가 안정적이고 유지 관리 가능한 상태로 유지되도록 할 수 있습니다.

단위 테스트 프로젝트 만들기

  1. 솔루션 탐색기에서 솔루션을 마우스 오른쪽 단추로 클릭합니다.
  2. 새 프로젝트>...를 선택합니다.
  3. WinUI 단위 테스트 앱 템플릿을 선택하고 다음을 선택합니다.
  4. 프로젝트 WinUINotes.Tests 이름을 지정하고 선택한 다음,만들기를 선택합니다.

프로젝트 참조 추가

  1. WinUINotes.Tests 프로젝트를 마우스 오른쪽 단추로 클릭하고프로젝트 참조>...를 선택합니다.
  2. WinUINotes.Bus 프로젝트를 선택하고 확인을 선택합니다.

테스트를 위한 가짜 구현 만들기

테스트를 위해 실제로 디스크에 쓰지 않는 파일 서비스 및 스토리지 클래스의 가짜 구현을 만듭니다. Fakes는 테스트를 위해 실제 종속성의 동작을 시뮬레이션하는 간단한 구현입니다.

  1. WinUINotes.Tests 프로젝트에서 Fakes라는 새 폴더를 만듭니다.

  2. 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를 시뮬레이션하기 위해 함께 작동하는 FakeStorageFolderFakeStorageFile 인스턴스를 반환합니다.
  3. 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동일한 방식으로 작업할 수 있습니다. 대부분의 인터페이스 멤버는 테스트에서 실제로 사용되는 속성과 메서드만 구현해야 하므로 throw NotImplementedException 합니다.

    이 자습서의 FakeStorageFolder에서 전체 구현 을 볼 수 있습니다.

  4. 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테스트 중인 코드에서 실제로 사용되는 멤버만 구현합니다.

    이 자습서의 FakeStorageFolder에서 전체 구현 을 볼 수 있습니다.

다음 문서에서 자세히 알아보세요.

간단한 단위 테스트 작성

  1. 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 없이 빠르게 실행되며 부작용 없이 반복적으로 실행할 수 있습니다.

다음 문서에서 자세히 알아보세요.

테스트 실행

  1. Visual Studio에서 테스트 탐색기 창을 엽니다(테스트>탐색기).
  2. 모든 테스트 실행을 선택하여 단위 테스트를 실행합니다.
  3. 테스트가 통과했는지 확인합니다.

이제 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>를 탐색합니다.