시뮬레이션을 검증하는 더 나은 방법(XUnit, Moq,.NET)
8096 단어 csharptestingdotnetunittesting
본고에서 저는 단원 테스트의 장점, 예를 들어 더 좋은 코드 품질, 실행 가능한 문서, 복잡한 업무 장면을 신속하게 집행하기 쉽다는 것을 소개하지 않겠습니다.나는 대부분의 상황에서 단원 테스트가 유익하고 심지어 강제적이라는 것을 발견했다.
예제 코드
단원 테스트를 시작하려면 코드가 필요합니다.나는 하나의 방법으로 간단한 클래스를 만들었다.클래스의 방법은 새로운 순서를 만드는 논리를 포함합니다.
public class OrderService
{
private readonly IItemRepository _itemRepository;
private readonly IOrderRepository _orderRepository;
public OrderService(
IItemRepository itemRepository,
IOrderRepository orderRepository)
{
_itemRepository = itemRepository;
_orderRepository = orderRepository;
}
public async Task CreateAsync(int itemId, int itemQuantity)
{
var item = await _itemRepository.GetAsync(itemId);
if (item.Stock < itemQuantity)
throw new Exception($"Item id=[{itemId}] has not enough stock for order.");
Order order = new()
{
ItemId = item.Id,
Quantity = itemQuantity
};
await _orderRepository.CreateAsync(order);
}
}
데이터베이스에서 데이터를 검색한다고 가정하십시오.이러한 종속 관계는 저장소로 추상화됩니다.추상적인 의존 관계는 단원 테스트에서의 시뮬레이션에 필수적이다.테스트 코드
나는 즐거움 경로 단원 테스트부터 시작하는 것을 좋아한다.우선 테스트 샘플 코드부터 시작하겠습니다.
public class OrderServiceTests
{
private readonly OrderService _sut;
private readonly Mock<IItemRepository> _itemRepositoryMock;
private readonly Mock<IOrderRepository> _orderRepositoryMock;
public OrderServiceTests()
{
_itemRepositoryMock = new Mock<IItemRepository>();
_orderRepositoryMock = new Mock<IOrderRepository>();
_sut = new OrderService(
_itemRepositoryMock.Object,
_orderRepositoryMock.Object);
}
[Fact]
public void CreateAsync_ShouldCreateNewOrder()
{
Assert.False(true);
}
}
만약 코드 구축에 성공했고, 우리가 실패한 테스트 결과를 얻었다면, 우리는 모두 준비가 되었다.다음 단계는 즐거움 경로 단원 테스트를 실현하는 것이다.[Fact]
public async Task CreateAsync_ShouldCreateNewOrder()
{
const int itemId = 1;
const int quantity = 2;
const int existingItemStock = 3;
_itemRepositoryMock.Setup(m => m.GetAsync(It.Is<int>(i => i == itemId)))
.ReturnsAsync(() => new Item
{
Id = itemId,
Stock = existingItemStock
});
await _sut.CreateAsync(itemId, quantity);
_itemRepositoryMock.Verify(m => m.GetAsync(itemId));
}
이 테스트를 분석해 봅시다.나는 모든 단원 테스트에서 AAA(배열, 동작, 단언) 모드를 사용한다.이것은 업계 표준으로 테스트를 구축하는 깨끗한 방법이다.await _orderRepository.CreateAsync(order);
.나는 _orderRepositry
시뮬레이션을 설정하지 않았다.Moq의 기본 시뮬레이션 동작으로 인해 발생합니다.시뮬레이션을 만들 때 비헤이비어가 기본값으로 설정됩니다.이 작업을 수행하는 방법은 Moq의 소스 코드에서 확인할 수 있습니다./// <summary>
/// Initializes an instance of the mock with <see cref="MockBehavior.Default"/> behavior.
/// </summary>
/// <example>
/// <code>
/// var mock = new Mock<IFormatProvider>;();
/// </code>
/// </example>
public Mock(): this(MockBehavior.Default)
{
}
MockBehavior
는 생성된 시뮬레이션 동작을 지정하는 열거입니다.사용 가능한 가치와 동작은 다음과 같습니다.Strict: 일치하는 구성이 없는 경우 메서드나 속성을 호출할 때마다 예외가 발생합니다.
느슨함: Moq는 모든 호출을 받아들이고 유효한 반환 값을 만들려고 시도합니다.
기본값: Loose와 같습니다.
MockBehavior
을 통해 확보할 수 있다.이것은 아날로그 호출을 설정해야 한다. public OrderServiceTests()
{
_itemRepositoryMock = new Mock<IItemRepository>(MockBehavior.Strict);
_orderRepositoryMock = new Mock<IOrderRepository>(MockBehavior.Strict);
_sut = new OrderService(_itemRepositoryMock.Object, _orderRepositoryMock.Object);
}
아날로그 호출을 설정하지 않으면 다음 메시지가 표시됩니다.Moq.MockException
IOrderRepository.CreateAsync(Order) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
....
테스트를 복구하기 위해서 IOrderRepository
의 설정을 추가해야 합니다.[Fact]
public async Task CreateAsync_ShouldCreateNewOrder()
{
const int itemId = 1;
const int quantity = 2;
const int existingItemStock = 3;
_itemRepositoryMock.Setup(m => m.GetAsync(It.Is<int>(i => i == itemId)))
.ReturnsAsync(() => new Item
{
Id = itemId,
Stock = existingItemStock
});
_orderRepositoryMock.Setup(m => m.CreateAsync(It.IsAny<Order>())).Returns(Task.CompletedTask);
await _sut.CreateAsync(itemId, quantity);
_itemRepositoryMock.Verify(m => m.GetAsync(itemId));
_orderRepositoryMock.Verify(m => m.CreateAsync(It.IsAny<Order>()));
}
이 복구 후에 더 많은 검증 코드를 도입하고 있음을 보실 수 있습니다.설정이 여러 개 있으면 모든 검증을 실행해야 합니다.만약 코드가 더욱 복잡하고 여러 가지 방법이 호출된다면, 이것은 복잡성과 피할 수 있는 자질구레한 코드를 가져올 것이다.이것은 시뮬레이션 실례 MockRepository
클래스를 만들고 관리하는 데 도움을 줄 수 있다.이 종류는 시뮬레이션을 한 곳에서 만들고 검증하는 데 도움을 줍니다.public class OrderServiceTests
{
private readonly OrderService _sut;
private readonly MockRepository _mockRepository;
private readonly Mock<IItemRepository> _itemRepositoryMock;
private readonly Mock<IOrderRepository> _orderRepositoryMock;
public OrderServiceTests()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_itemRepositoryMock = _mockRepository.Create<IItemRepository>();
_orderRepositoryMock = _mockRepository.Create<IOrderRepository>();
_sut = new OrderService(_itemRepositoryMock.Object, _orderRepositoryMock.Object);
}
MockRepository를 사용하면 우리는 같은 시뮬레이션 행위를 설정했고 VerifyAll()
방법으로 모든 호출을 검증할 수 있다. [Fact]
public async Task CreateAsync_ShouldCreateNewOrder()
{
//test code
//_itemRepositoryMock.Verify(m => m.GetAsync(itemId));
//_orderRepositoryMock.Verify(m => m.CreateAsync(It.IsAny<Order>()));
_mockRepository.VerifyAll();
}
더욱 진일보하기 위해서 우리는 Dispose()
방법을 사용하여 시뮬레이션을 검증할 수 있다.Dispose()
XUnit를 사용하여 모든 테스트 후에 실행하기 때문에 우리는 설정된 모든 시뮬레이션을 검증할 수 있고 모든 테스트 방법에서 _mockRepository.VerifyAll()
코드 줄을 피할 수 있다.public class OrderServiceTests : IDisposable
{
private readonly OrderService _sut;
private readonly MockRepository _mockRepository;
private readonly Mock<IItemRepository> _itemRepositoryMock;
private readonly Mock<IOrderRepository> _orderRepositoryMock;
public OrderServiceTests(){...}
[Fact]
public async Task CreateAsync_ShouldCreateNewOrder(){...}
public void Dispose()
{
_mockRepository.VerifyAll();
}
}
이렇게 해서, 우리는 모크를 검증하고 테스트 코드를 깔끔하게 유지하는 간결한 방법을 가지게 되었다.전체 코드here를 찾을 수 있습니다.
최저 주문량에 대한 더 많은 정보는 방문해 주십시오here.
Reference
이 문제에 관하여(시뮬레이션을 검증하는 더 나은 방법(XUnit, Moq,.NET)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/audmasiulionis/a-better-way-to-verify-mocks-xunit-moq-net-imp텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)