.net core grpc单元测试 – 服务器端

.net core grpc单元测验 - 服务器

前语
gRPC凭仗其谨慎的接口界说、高效的传输功率、多样的调用办法等长处,在微服务开发方面占有了一席之地。dotnet core正式支撑gRPC也有一段时间了,官方文档也对怎么运用gRPC进行了比较详细的阐明,可是关于怎么对gRPC的服务器和客户端进行单元测验,却没有描绘。经过查阅官方代码,找到了一些解决办法,总结在此,供我们参阅。

本文要点介绍gRPC服务器端代码的单元测验,包括一般调用、服务器端流、客户端流等调用办法的单元测验,别的,引进sqlite的内存数据库形式,对数据库相关操作进行测验。

预备gRPC服务端项目
运用dotnet new grpc指令创立一个gRPC服务器项目。

修正protos/greeter.proto, 增加两个接口办法:

//服务器流
rpc SayHellos (HelloRequest) returns (stream HelloReply);

//客户端流
rpc Sum (stream HelloRequest) returns (HelloReply);

在GreeterService中增加办法的完成:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using GrpcTest.Server.Models;
using Microsoft.Extensions.Logging;

namespace GrpcTest.Server
{

public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
private readonly ApplicationDbContext _db;
public GreeterService(ILogger<GreeterService> logger,
ApplicationDbContext db)
{
_logger = logger;
_db = db;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
public override async Task SayHellos(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
foreach (var student in _db.Students)
{
if (context.CancellationToken.IsCancellationRequested)
break;
var message = student.Name;
_logger.LogInformation($"Sending greeting {message}.");
await responseStream.WriteAsync(new HelloReply { Message = message });
}
}
public override async Task<HelloReply> Sum(IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
{
var sum = 0;
await foreach (var request in requestStream.ReadAllAsync())
{
if (int.TryParse(request.Name, out var number))
sum += number;
else
throw new ArgumentException("参数有必要是可辨认的数字");
}
return new HelloReply { Message = $"sum is {sum}" };
}
}

}

SayHello: 简略的回来一个文本音讯。

SayHellos: 从数据库的表中读取一切数据,而且运用服务器端流的办法回来。

Sum:从客户端流获取输入数据,并核算一切数据的和,假如输入的文本无法转换为数字,抛出反常。

单元测验
新建xunit项目,并引证方才树立的gRPC项目,引进如下包:

<PackageReference Include="Grpc.Core.Testing" Version="2.28.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="moq" Version="4.14.1" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />

假造Logger
运用如下指令假造service需求的logger:
var logger = Mock.Of>();
运用sqlite inmemory的DbContext

public static ApplicationDbContext CreateDbContext(){

        var db = new ApplicationDbContext(new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(CreateInMemoryDatabase()).Options);
db.Database.EnsureCreated();
return db;
}
private static DbConnection CreateInMemoryDatabase()
{
var connection = new SqliteConnection("Filename=:memory:");
connection.Open();
return connection;
}

要点:虽然是内存形式,数据库也有必要是open的,而且需求运转EnsureCreated,不然调用数据库功用是会陈述找不到表。

假造ServerCallContext
运用如下代码假造:

public static ServerCallContext CreateTestContext(){

        return TestServerCallContext.Create("fooMethod",
null,
DateTime.UtcNow.AddHours(1),
new Metadata(),
CancellationToken.None,
"127.0.0.1",
null,
null,
(metadata) => TaskUtils.CompletedTask,
() => new WriteOptions(),
(writeOptions) => { });

}

里边的详细参数要根据实践测验需求进行调整,比方测验客户端撤销操作时,修正CancellationToken参数。

一般调用的测验

[Fact]

    public void SayHello()
{
var service = new GreeterService(logger, null);
var request = new HelloRequest{Name="world"};
var response = service.SayHello(request, scc).Result;
var expected = "Hello world";
var actual = response.Message;
Assert.Equal(expected, actual);
}

其间scc = 假造的ServerCallContext,假如被测办法中没有实践运用它,也能够直接传入null。

服务器端流的测验
服务器端流的办法包括一个IServerStreamWriter类型的参数,该参数被用于将办法的核算结果逐一回来给调用方,能够创立一个通用的类完成此接口,将写入的音讯存储为一个list,以便测验。

public class TestServerStreamWriter : IServerStreamWriter
{

public WriteOptions WriteOptions { get; set; }
public List<T> Responses { get; } = new List<T>();
public Task WriteAsync(T message)
{
this.Responses.Add(message);
return Task.CompletedTask;
}

}

测验时,向数据库表中刺进两条记载,然后测验比照,看接口办法是否回来两条记载。

public async Task SayHellos(){

        var db = TestTools.CreateDbContext();
var students = new List<Student>{
new Student{Name="1"},
new Student{Name="2"}
};
db.AddRange(students);
db.SaveChanges();
var service = new GreeterService(logger, db);
var request = new HelloRequest{Name="world"};
var sw = new TestServerStreamWriter<HelloReply>();
await service.SayHellos(request, sw, scc);
var expected = students.Count;
var actual = sw.Responses.Count;
Assert.Equal(expected, actual);

}

客户端流的测验
与服务器流相似,客户端流办法也有一个参数类型为IAsyncStreamReader,简略完成一个类用于测验。

该类经过直接将客户端要传入的数据经过IEnumable参数传入,模仿客户端的流式恳求多个数据。

public class TestStreamReader : IAsyncStreamReader
{

private readonly IEnumerator<T> _stream;
public TestStreamReader(IEnumerable<T> list){
_stream = list.GetEnumerator();
}
public T Current => _stream.Current;
public Task<bool> MoveNext(CancellationToken cancellationToken)
{
return Task.FromResult(_stream.MoveNext());
}

}

正常流程测验代码

[Fact]

    public void Sum_NormalInput_ReturnSum()
{
var service = new GreeterService(null, null);
var data = new List<HelloRequest>{
new HelloRequest{Name="1"},
new HelloRequest{Name="2"},
};
var stream = new TestStreamReader<HelloRequest>(data);
var response = service.Sum(stream, scc).Result;
var expected = "sum is 3";
var actual = response.Message;
Assert.Equal(expected, actual);
}

参数过错的测验代码

[Fact]

    public void Sum_BadInput_ThrowException()
{
var service = new GreeterService(null, null);
var data = new List<HelloRequest>{
new HelloRequest{Name="1"},
new HelloRequest{Name="abc"},
};
var stream = new TestStreamReader<HelloRequest>(data);
Assert.ThrowsAsync<ArgumentException>(async () => await service.Sum(stream, scc));
}

总结
以上代码,经过对gRPC服务依靠的要害资源进行mock或简略完成,达到了单元测验的意图。

原文地址https://www.cnblogs.com/wjsgzcn/p/12883169.html