단위 테스트
팁
이 콘텐츠는 ‘.NET MAUI를 사용하는 엔터프라이즈 애플리케이션 패턴’ eBook에서 발췌한 것으로, .NET Docs에서 제공되거나 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공됩니다.
다중 플랫폼 앱은 데스크톱 및 웹 기반 애플리케이션과 비슷한 문제를 경험합니다. 모바일 사용자는 디바이스, 네트워크 연결, 서비스 가용성 및 기타 다양한 요인에 따라 달라집니다. 따라서 다중 플랫폼 앱은 품질, 안정성 및 성능을 개선하기 위해 실제 환경에서 사용되므로 테스트를 거쳐야 합니다. 단위 테스트, 통합 테스트 및 사용자 인터페이스 테스트를 비롯한 많은 유형의 테스트를 앱에서 수행해야 합니다. 단위 테스트는 가장 일반적인 형태이며 고품질 애플리케이션을 빌드하는 데 필수적입니다.
단위 테스트는 앱의 작은 단위(일반적으로 메서드)를 사용하고, 코드의 나머지 부분에서 격리하고, 예상대로 동작하는지 확인합니다. 각 기능 단위가 예상대로 수행되므로 앱 전체에서 오류가 전파되지 않는지 확인하는 것이 목표입니다. 버그가 발생하는 위치를 감지하는 것이 보조 실패 지점에서 버그의 효과를 간접적으로 관찰하는 것보다 더 효율적입니다.
단위 테스트는 소프트웨어 개발 워크플로의 핵심 요소인 경우 코드 품질에 가장 큰 영향을 미칩니다. 단위 테스트는 애플리케이션에 대한 디자인 설명서 및 기능 사양으로 작동할 수 있습니다. 메서드가 작성되는 즉시 표준, 경계 및 잘못된 입력 데이터 사례에 대한 응답으로 메서드의 동작을 확인하고, 코드에서 수행한 명시적 또는 암시적 가정을 확인하는 단위 테스트를 작성해야 합니다. 또는 테스트 기반 개발을 통해 단위 테스트가 코드보다 먼저 기록됩니다. 테스트 기반 개발 및 구현 방법에 대한 자세한 내용은 연습: 테스트 탐색기를 사용한 테스트 기반 개발을 참조하세요.
참고
단위 테스트는 회귀에 매우 효과적입니다. 즉, 이전에는 작동했지만 잘못된 업데이트로 인해 방해를 받은 기능입니다.
단위 테스트는 일반적으로 arrange-act-assert 패턴을 사용합니다.
단계 | 설명 |
---|---|
정렬 | 개체를 초기화하고 테스트 중인 메서드에 전달되는 데이터의 값을 설정합니다. |
행동 | 필수 인수를 사용하여 테스트 중인 메서드를 호출합니다. |
Assert | 테스트 중인 메서드의 작업이 예상한 대로 작동하는지 확인합니다. |
이 패턴은 단위 테스트를 읽을 수 있고, 자체적으로 설명하고, 일관되도록 합니다.
종속성 주입 및 단위 테스트
느슨하게 결합된 아키텍처를 채택하는 동기 중 하나는 단위 테스트를 용이하게 한다는 것입니다. 종속성 주입 서비스에 등록된 형식 중 하나는 IAppEnvironmentService
인터페이스입니다. 다음 코드 예제에서는 이 클래스의 개요를 보여 줍니다.
public class OrderDetailViewModel : ViewModelBase
{
private IAppEnvironmentService _appEnvironmentService;
public OrderDetailViewModel(
IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
: base(dialogService, navigationService, settingsService)
{
_appEnvironmentService = appEnvironmentService;
}
}
OrderDetailViewModel
클래스는 여기서 종속성 주입 컨테이너가 OrderDetailViewModel
개체를 인스턴스화할 때 확인하는 IAppEnvironmentService
형식에 종속됩니다. 그러나 단위 테스트를 수행할 실제 서버, 디바이스 및 구성을 활용하는 IAppEnvironmentService
개체를 만드는 대신, OrderDetailViewModel
클래스는 IAppEnvironmentService
개체를 테스트를 위한 모의 개체로 바꿉니다. 모의 개체는 개체 또는 인터페이스의 서명이 동일하지만 단위 테스트에 도움이 되는 특정 방식으로 만들어지는 개체입니다. 이 기능은 종속성 주입과 함께 다른 데이터 및 워크플로 시나리오를 테스트하기 위한 특정 인터페이스 구현을 제공하는 데 자주 사용됩니다.
이 방법을 사용하면 런타임에 IAppEnvironmentService
개체를 OrderDetailViewModel
클래스로 전달할 수 있으며 테스트 가능성을 위해 모의 클래스를 테스트 타임에 OrderDetailViewModel
클래스로 전달할 수 있습니다. 이 방법의 주요 이점은 런타임 플랫폼 기능, 웹 서비스 또는 데이터베이스와 같은 다루기 힘든 리소스 없이 단위 테스트를 실행할 수 있다는 것입니다.
MVVM 애플리케이션 테스트
MVVM 애플리케이션에서 모델 및 뷰 모델을 테스트하는 것은 다른 클래스를 테스트하는 것과 동일하며, 동일한 도구와 기술을 사용합니다. 여기에는 단위 테스트 및 모의와 같은 기능이 포함됩니다. 그러나 모델 및 뷰 모델 클래스에 일반적인 일부 패턴은 특정 단위 테스트 기술을 활용할 수 있습니다.
팁
각 단위 테스트로 한 가지 사항을 테스트합니다. 테스트가 복잡해짐에 따라 해당 테스트의 검증이 더 어려워집니다. 단위 테스트를 단일 관심사로 제한하여 테스트를 좀 더 반복 가능하고 격리되고 더 빠르게 실행되게 만들 수 있습니다. 더 많은 모범 사례는 .NET을 사용한 단위 테스트 모범 사례를 참조하세요.
단위 테스트 연습을 둘 이상의 단위 동작으로 만드는 것은 바람직하지 않습니다. 이렇게 하면 테스트를 업데이트하기 어려울 수 있습니다. 또한 오류를 해석할 때 혼동을 초래할 수 있습니다.
eShop 다중 플랫폼 앱은 MSTest를 사용하여 두 가지 유형의 단위 테스트를 지원하는 단위 테스트를 수행합니다.
테스트 유형 | attribute | 설명 |
---|---|---|
TestMethod | TestMethod |
실행할 실제 테스트 메서드를 정의합니다. |
DataSource | DataSource |
특정 데이터 세트에 대해서만 해당되는 테스트입니다. |
eShop 다중 플랫폼 앱에 포함된 단위 테스트는 TestMethod이므로 각 단위 테스트 메서드는 특성으로 TestMethod
데코레이팅됩니다. MSTest 외에도 NUnit 및 xUnit을 비롯한 여러 가지 다른 테스트 프레임워크를 사용할 수 있습니다.
비동기 기능 테스트
MVVM 패턴을 구현할 때 뷰 모델은 일반적으로 비동기적으로 서비스에 대한 작업을 호출합니다. 이러한 작업을 호출하는 코드에 대한 테스트는 일반적으로 모의 버전을 실제 서비스의 대체 항목으로 사용합니다. 다음 코드 예제에서는 모의 서비스를 뷰 모델에 전달하여 비동기 기능을 테스트하는 방법을 보여 줍니다.
[TestMethod]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
// Arrange
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
// Act
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
// Assert
Assert.IsNotNull(orderViewModel.Order);
}
이 단위 테스트는 InitializeAsync
메서드가 호출된 후 OrderDetailViewModel
인스턴스의 Order
속성에 값이 있는지 확인합니다. 뷰 모델의 해당 뷰를 탐색할 때 InitializeAsync
메서드가 호출됩니다. 탐색에 대한 자세한 내용은 탐색을 참조하세요.
OrderDetailViewModel
인스턴스가 만들어지면 IOrderService
인스턴스가 인수로 지정되어야 합니다. 그러나 OrderService
는 웹 서비스에서 데이터를 검색합니다. 따라서 OrderMockService
인스턴스인 OrderService
클래스의 모의 버전은 OrderDetailViewModel
구문에 대한 인수로 지정됩니다. 그런 다음, IOrderService
작업을 사용하는 뷰 모델의 InitializeAsync
메서드가 호출될 때 웹 서비스와 통신하지 않고 모의 데이터가 검색됩니다.
INotifyPropertyChanged 구현 테스트
INotifyPropertyChanged
인터페이스를 구현하면 뷰 모델 및 모델에서 시작된 변경에 뷰가 대응할 수 있습니다. 이러한 변경은 컨트롤에 표시된 데이터로 제한되지 않습니다. 애니메이션을 시작하거나 컨트롤을 사용하지 않도록 설정할 수 있는 뷰 모델 상태와 같이 뷰를 제어하는 데도 사용됩니다.
단위 테스트에서 직접 업데이트할 수 있는 속성은 이벤트 처리기를 PropertyChanged
이벤트에 연결하고 속성의 새 값을 설정한 후 이벤트가 발생하는지 여부를 확인하여 테스트할 수 있습니다. 다음 코드 예제에서는 이러한 테스트를 보여 줍니다.
[TestMethod]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
var invoked = false;
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
orderViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Order"))
invoked = true;
};
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.IsTrue(invoked);
}
이 단위 테스트는 OrderViewModel
클래스의 InitializeAsync
메서드를 호출합니다. 그러면 해당 Order
속성이 업데이트됩니다. 단위 테스트는 PropertyChanged
이벤트가 Order
속성에 대해 발생하는 경우 통과입니다.
메시지 기반 통신 테스트
다음 코드 예제와 같이 클래스를 사용하여 MessagingCenter
느슨하게 결합된 클래스 간에 통신하는 뷰 모델은 테스트 중인 코드에서 보내는 메시지를 구독하여 단위 테스트를 수행할 수 있습니다.
[TestMethod]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
var messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.IsTrue(messageReceived);
}
이 단위 테스트는 CatalogViewModel
이 실행 중인 해당 AddCatalogItemCommand
에 대한 응답으로 AddProduct
메시지를 게시하는지 확인합니다. MessagingCenter
클래스는 멀티캐스트 메시지 구독을 지원하므로 단위 테스트는 AddProduct
메시지를 구독하고 수신에 대한 응답으로 콜백 대리자를 실행할 수 있습니다. 람다 식으로 지정된 이 콜백 대리자는 Assert
문에서 테스트 동작을 확인하는 데 사용하는 부울 필드를 설정합니다.
예외 처리 테스트
다음 코드 예제와 같이 잘못된 작업 또는 입력에 대해 특정 예외가 throw되는지 확인하는 단위 테스트를 작성할 수도 있습니다.
[TestMethod]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
이 단위 테스트는 ListView
컨트롤에 OnItemTapped
이벤트가 없으므로 예외를 throw합니다. Assert.Throws<T>
메서드는 T
가 예상되는 예외의 형식인 제네릭 메서드입니다. Assert.Throws<T>
메서드에 전달된 인수는 예외를 throw하는 람다 식입니다. 따라서 단위 테스트는 람다 식이 ArgumentException
을 throw하는 경우 통과입니다.
팁
예외 메시지 문자열을 검사하는 단위 테스트를 작성하지 마세요. 예외 메시지 문자열은 시간이 지남에 따라 변경될 수 있으므로 현재 상태에 의존하는 단위 테스트는 불안정한 것으로 간주됩니다.
유효성 검사 테스트
유효성 검사 구현을 테스트하는 두 가지 측면은 유효성 검사 규칙이 올바르게 구현되었는지 테스트하고 ValidatableObject<T>
클래스가 예상대로 작동하는지 테스트하는 것입니다.
일반적으로 출력이 입력에 따라 달라지는 자체 포함 프로세스이므로 유효성 검사 논리는 일반적으로 간단히 테스트할 수 있습니다. 다음 코드 예제와 같이 하나 이상의 연결된 유효성 검사 규칙이 있는 각 속성에 대해 Validate
메서드를 호출한 결과를 테스트해야 합니다.
[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
var isValid = mockViewModel.Validate();
Assert.IsTrue(isValid);
}
이 단위 테스트는 MockViewModel
인스턴스의 두 ValidatableObject<T>
속성에 모두 데이터가 있는 경우 유효성 검사가 성공하는지 확인합니다.
유효성 검사에 성공하는지 확인할 뿐만 아니라 유효성 검사 단위 테스트는 각 ValidatableObject<T>
인스턴스의 Value
, IsValid
및 Errors
속성 값을 확인하여 클래스가 예상대로 작동하는지 알아봅니다. 다음 코드 예제에서는 이 작업을 수행하는 단위 테스트를 보여 줍니다.
[TestMethod]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.IsFalse(isValid);
Assert.IsNotNull(mockViewModel.Forename.Value);
Assert.IsNull(mockViewModel.Surname.Value);
Assert.IsTrue(mockViewModel.Forename.IsValid);
Assert.IsFalse(mockViewModel.Surname.IsValid);
Assert.AreEqual(mockViewModel.Forename.Errors.Count(), 0);
Assert.AreNotEqual(mockViewModel.Surname.Errors.Count(), 0);
}
이 단위 테스트는 의 속성에 MockViewModel
의 Surname
속성에 데이터가 없고 각 ValidatableObject<T>
의 Value
, IsValid
및 Errors
속성이 올바르게 설정되면 유효성 검사가 실패했는지 확인합니다.
요약
단위 테스트는 앱의 작은 단위(일반적으로 메서드)를 사용하고, 코드의 나머지 부분에서 격리하고, 예상대로 동작하는지 확인합니다. 각 기능 단위가 예상대로 수행되므로 앱 전체에서 오류가 전파되지 않는지 확인하는 것이 목표입니다.
종속 개체를 종속 개체의 동작을 시뮬레이트하는 모의 개체로 바꿔 테스트 중인 개체의 동작을 격리할 수 있습니다. 이렇게 하면 런타임 플랫폼 기능, 웹 서비스 또는 데이터베이스와 같은 다루기 힘든 리소스 없이 단위 테스트를 실행할 수 있다는 것입니다.
MVVM 애플리케이션에서 모델 및 뷰 모델을 테스트하는 것은 다른 클래스를 테스트하는 것과 동일하며 동일한 도구와 기술을 사용할 수 있습니다.
.NET