Kiểm thử dịch vụ gRPC trong ASP.NET Core
Kiểm thử là một khía cạnh quan trọng trong việc xây dựng phần mềm ổn định và có thể bảo trì. Bài viết này thảo luận về cách kiểm thử các dịch vụ gRPC trong ASP.NET Core.
Có ba cách tiếp cận phổ biến để kiểm thử dịch vụ gRPC:
- Unit testing (kiểm thử đơn vị): Kiểm thử dịch vụ gRPC trực tiếp từ thư viện unit testing.
- Integration testing (kiểm thử tích hợp): Ứng dụng gRPC được lưu trú trong TestServer, một in-memory test server (server kiểm thử trong bộ nhớ) từ gói
Microsoft.AspNetCore.TestHost. Các dịch vụ gRPC được kiểm thử bằng cách gọi chúng qua gRPC client từ thư viện unit testing. - Manual testing (kiểm thử thủ công): Kiểm thử gRPC server bằng các lời gọi đặc biệt. Để biết thông tin về cách sử dụng công cụ dòng lệnh và giao diện với dịch vụ gRPC, xem Test gRPC services with gRPCurl and gRPCui in ASP.NET Core.
Trong unit testing, chỉ có dịch vụ gRPC tham gia. Các dependency (phụ thuộc) được inject vào dịch vụ phải được mock (giả lập). Trong integration testing, dịch vụ gRPC và cơ sở hạ tầng phụ trợ của nó đều là một phần của bài kiểm thử. Điều này bao gồm khởi động ứng dụng, dependency injection (DI), routing (định tuyến), authentication (xác thực) và authorization (phân quyền).
Dịch vụ mẫu có thể kiểm thử
Để minh họa việc kiểm thử dịch vụ, hãy xem xét dịch vụ sau trong ứng dụng mẫu.
TesterService trả về lời chào sử dụng bốn loại phương thức của gRPC.
public class TesterService : Tester.TesterBase
{
private readonly IGreeter _greeter;
public TesterService(IGreeter greeter)
{
_greeter = greeter;
}
public override Task<HelloReply> SayHelloUnary(HelloRequest request,
ServerCallContext context)
{
var message = _greeter.Greet(request.Name);
return Task.FromResult(new HelloReply { Message = message });
}
public override async Task SayHelloServerStreaming(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
var i = 0;
while (!context.CancellationToken.IsCancellationRequested)
{
var message = _greeter.Greet($"{request.Name} {++i}");
await responseStream.WriteAsync(new HelloReply { Message = message });
await Task.Delay(1000);
}
}
public override async Task<HelloReply> SayHelloClientStreaming(
IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
{
var names = new List<string>();
await foreach (var request in requestStream.ReadAllAsync())
{
names.Add(request.Name);
}
var message = _greeter.Greet(string.Join(", ", names));
return new HelloReply { Message = message };
}
public override async Task SayHelloBidirectionalStreaming(
IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
await foreach (var request in requestStream.ReadAllAsync())
{
await responseStream.WriteAsync(
new HelloReply { Message = _greeter.Greet(request.Name) });
}
}
}Dịch vụ gRPC trước đó:
- Tuân theo Explicit Dependencies Principle.
- Mong đợi dependency injection (DI) cung cấp một instance của
IGreeter. - Có thể được kiểm thử với
IGreeterservice được mock bằng mock object framework (framework đối tượng giả lập), chẳng hạn như Moq. Mocked object (đối tượng được giả lập) là một đối tượng được chế tạo với tập hợp hành vi property và method được xác định trước dùng cho kiểm thử.
Unit test dịch vụ gRPC
Thư viện unit test có thể kiểm thử trực tiếp các dịch vụ gRPC bằng cách gọi các phương thức của nó. Unit test kiểm thử một dịch vụ gRPC một cách độc lập.
[Fact]
public async Task SayHelloUnaryTest()
{
// Arrange
var mockGreeter = new Mock<IGreeter>();
mockGreeter.Setup(
m => m.Greet(It.IsAny<string>())).Returns((string s) => $"Hello {s}");
var service = new TesterService(mockGreeter.Object);
// Act
var response = await service.SayHelloUnary(
new HelloRequest { Name = "Joe" }, TestServerCallContext.Create());
// Assert
mockGreeter.Verify(v => v.Greet("Joe"));
Assert.Equal("Hello Joe", response.Message);
}Unit test trước đó:
- Mock
IGreeterbằng Moq. - Thực thi phương thức
SayHelloUnaryvới request message vàServerCallContext. Tất cả các phương thức dịch vụ đều có tham sốServerCallContext. Trong bài kiểm thử này, kiểu được cung cấp bằng phương thức trợ giúpTestServerCallContext.Create(). Phương thức trợ giúp này được bao gồm trong code mẫu. - Thực hiện assertions (khẳng định):
- Xác minh tên request được truyền vào
IGreeter. - Dịch vụ trả về reply message như mong đợi.
Unit test HttpContext trong các phương thức gRPC
Các phương thức gRPC có thể truy cập HttpContext của request sử dụng extension method ServerCallContext.GetHttpContext. Để unit test một phương thức sử dụng HttpContext, context phải được cấu hình trong thiết lập kiểm thử. Nếu HttpContext không được cấu hình thì GetHttpContext trả về null.
Để cấu hình HttpContext trong thiết lập kiểm thử, tạo một instance mới và thêm nó vào collection ServerCallContext.UserState bằng khóa __HttpContext.
var httpContext = new DefaultHttpContext(); var serverCallContext = TestServerCallContext.Create(); serverCallContext.UserState["__HttpContext"] = httpContext;
Thực thi các phương thức dịch vụ với call context này để sử dụng instance HttpContext đã được cấu hình.
Integration test dịch vụ gRPC
Integration test đánh giá các thành phần của ứng dụng ở mức độ rộng hơn unit test. Ứng dụng gRPC được lưu trú trong TestServer, một in-memory test server từ gói Microsoft.AspNetCore.TestHost.
Thư viện unit test khởi động ứng dụng gRPC và sau đó các dịch vụ gRPC được kiểm thử bằng gRPC client.
Code mẫu chứa cơ sở hạ tầng để làm cho integration testing có thể thực hiện:
- Class
GrpcTestFixture<TStartup>cấu hình ASP.NET Core host và khởi động ứng dụng gRPC trong in-memory test server. - Class
IntegrationTestBaselà kiểu cơ sở mà các integration test kế thừa. Nó chứa trạng thái fixture và các API để tạo gRPC client để gọi ứng dụng gRPC.
[Fact]
public async Task SayHelloUnaryTest()
{
// Arrange
var client = new Tester.TesterClient(Channel);
// Act
var response = await client.SayHelloUnaryAsync(new HelloRequest { Name = "Joe" });
// Assert
Assert.Equal("Hello Joe", response.Message);
}Integration test trước đó:
- Tạo một gRPC client sử dụng channel được cung cấp bởi
IntegrationTestBase. Kiểu này được bao gồm trong code mẫu. - Gọi phương thức
SayHelloUnarysử dụng gRPC client. - Khẳng định dịch vụ trả về reply message như mong đợi.
Inject mock dependencies (inject phụ thuộc giả lập)
Sử dụng ConfigureWebHost trên fixture để ghi đè các dependency. Việc ghi đè dependency hữu ích khi external dependency (phụ thuộc bên ngoài) không khả dụng trong môi trường kiểm thử. Ví dụ, ứng dụng sử dụng payment gateway (cổng thanh toán) bên ngoài không nên gọi phụ thuộc bên ngoài khi thực thi kiểm thử. Thay vào đó, hãy sử dụng mock gateway cho bài kiểm thử.
public MockedGreeterServiceTests(GrpcTestFixture<Startup> fixture,
ITestOutputHelper outputHelper) : base(fixture, outputHelper)
{
var mockGreeter = new Mock<IGreeter>();
mockGreeter.Setup(
m => m.Greet(It.IsAny<string>())).Returns((string s) =>
{
if (string.IsNullOrEmpty(s))
{
throw new ArgumentException("Name not provided.");
}
return $"Test {s}";
});
Fixture.ConfigureWebHost(builder =>
{
builder.ConfigureServices(
services => services.AddSingleton(mockGreeter.Object));
});
}
[Fact]
public async Task SayHelloUnaryTest_MockGreeter_Success()
{
// Arrange
var client = new Tester.TesterClient(Channel);
// Act
var response = await client.SayHelloUnaryAsync(
new HelloRequest { Name = "Joe" });
// Assert
Assert.Equal("Test Joe", response.Message);
}Integration test trước đó:
- Trong constructor của test class (
MockedGreeterServiceTests): - Mock
IGreeterbằng Moq. - Ghi đè
IGreeterđược đăng ký với dependency injection sử dụngConfigureWebHost. - Gọi phương thức
SayHelloUnarysử dụng gRPC client. - Khẳng định reply message như mong đợi dựa trên instance
IGreeterđược mock.