C#/단위테스트
[C#] C# gRPC 단위테스트 하기
범범조조
2023. 3. 1. 03:37
참고
목적
- C# 으로 gRPC Server 를 구현합니다.
- 원래는 Client 가 Server 에게 메시지를 전송해서 서로 통신을 해야 하지만, Client 를 C# 에서 단위테스트를 통해서 구현하여 C# 에서 gRPC 단위테스트 하는 방법에 대해서 알아 봅니다.
gRPC 서버 구현
- C# 에서 gRPC 서버 구현하는 방법은
ASP.NET Core gRPC 서비스
프로젝트를 생성하게 되면, 기본으로greet.proto
파일과 함께 GreeterService 가 생성됩니다. - 아무런 작업을 하지 않아도 기본으로 gRPC 서버가 생성된 것입니다.
- 하지만, 여기서 저는 테스트를 위해 2가지 작업을 진행해 주었습니다.
- 첫 번째로,
IGreeter.cs
인터페이스를 생성하였고, 생성한IGreeter
인터페이스를 GreeterService 에 생성자로 의존성 주입을 시켜 주었습니다. - 추가 및 변경된 소스 코드는 다음과 같습니다.
IGreeter.cs
namespace gRPC_UnitTest
{
public interface IGreeter
{
string Greet(string name);
}
}
GreeterService.cs
using Grpc.Core;
namespace gRPC_UnitTest.Services
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
private readonly IGreeter _service;
public GreeterService(ILogger<GreeterService> logger, IGreeter service)
{
_logger = logger;
_service = service;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
var result = _service.Greet(request.Name);
return Task.FromResult(new HelloReply
{
Message = result
});
}
}
}
- 위와 같이
IGreeter
인터페이스와 인터페이스를 생성자에 서비스로 주입해 주었습니다. - 그럼 이제 위 gRPC 서버 코드를 단위테스트 하도록 단위 테스트 코드를 작성해 보도록 하겠습니다.
단위 테스트 준비
- 단위 테스트를 준비하려면
xUnit 테스트
프로젝트를 하나 생성합니다. - 그리고 gRPC 단위 테스트를 위한 Helper 클래스인
TestServerCallContext.cs
를 생성해 줍니다. - 그리고 아래와 같이 Mocking 을 이용하여
SayHello
메서드를 테스트 진행하는 코드를 작성해 보도록 하겠습니다.
TestServerCallContext.cs
using Grpc.Core;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace UnitTest
{
public class TestServerCallContext : ServerCallContext
{
private readonly Metadata _requestHeaders;
private readonly CancellationToken _cancellationToken;
private readonly Metadata _responseTrailers;
private readonly AuthContext _authContext;
private readonly Dictionary<object, object> _userState;
private WriteOptions? _writeOptions;
public Metadata? ResponseHeaders { get; private set; }
private TestServerCallContext(Metadata requestHeaders, CancellationToken cancellationToken)
{
_requestHeaders = requestHeaders;
_cancellationToken = cancellationToken;
_responseTrailers = new Metadata();
_authContext = new AuthContext(string.Empty, new Dictionary<string, List<AuthProperty>>());
_userState = new Dictionary<object, object>();
}
protected override string MethodCore => "MethodName";
protected override string HostCore => "HostName";
protected override string PeerCore => "PeerName";
protected override DateTime DeadlineCore { get; }
protected override Metadata RequestHeadersCore => _requestHeaders;
protected override CancellationToken CancellationTokenCore => _cancellationToken;
protected override Metadata ResponseTrailersCore => _responseTrailers;
protected override Status StatusCore { get; set; }
protected override WriteOptions? WriteOptionsCore { get => _writeOptions; set { _writeOptions = value; } }
protected override AuthContext AuthContextCore => _authContext;
protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions options)
{
throw new NotImplementedException();
}
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)
{
if (ResponseHeaders != null)
{
throw new InvalidOperationException("Response headers have already been written.");
}
ResponseHeaders = responseHeaders;
return Task.CompletedTask;
}
protected override IDictionary<object, object> UserStateCore => _userState;
public static TestServerCallContext Create(Metadata? requestHeaders = null, CancellationToken cancellationToken = default)
{
return new TestServerCallContext(requestHeaders ?? new Metadata(), cancellationToken);
}
}
}
단위 테스트 코드
- 위에서 단위 테스트에 필요한 Helper 클래스를 작성하였습니다.
- 그럼 이제 앞서 생성한 gRPC Server 인
GreeterService
를 단위 테스트 할 수 있도록 코드를 작성해 보겠습니다. - 다음 코드에서는 Mocking 을 이용한 단위 테스트 코드를 작성하였습니다.
- Act 부분에
Joe
라는 메시지를 전송하고, 최종적으로Hello Joe
하고 결과값이 맞는지 비교하여 맞으면 테스트를 통과하는 로직을 작성하였습니다. - 실제로 테스트를 실행하여 정상적으로 테스트가 진행 되는지 확인 하였습니다.
using gRPC_UnitTest;
using gRPC_UnitTest.Services;
using Microsoft.Extensions.Logging;
using Moq;
using System.Threading.Tasks;
using Xunit;
namespace UnitTest
{
public class UnitTest1
{
[Fact]
public async Task SayHelloTest()
{
// Arrange
var loggerFactory = new LoggerFactory();
ILogger<GreeterService> logger = loggerFactory.CreateLogger<GreeterService>();
var mockGreeter = new Mock<IGreeter>();
mockGreeter.Setup(
m => m.Greet(It.IsAny<string>())).Returns((string s) => $"Hello {s}");
var service = new GreeterService(logger, mockGreeter.Object);
// Act
var response = await service.SayHello(
new HelloRequest { Name = "Joe" }, TestServerCallContext.Create());
// Assert
mockGreeter.Verify(v => v.Greet("Joe"));
Assert.Equal("Hello Joe", response.Message);
}
}
}
실행 결과
- 실행 결과, 정상적으로 테스트 통과 되는 것을 확인할 수 있습니다.
728x90