.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);
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 = ;
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项目,引入如下包:
<ItemGroup>
<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" />
</ItemGroup>
伪造Logger
使用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(),
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<HelloReply>类型的参数,该参数被用于将方法的计算结果逐个返回给调用方,可以创建一个通用的类实现此接口,将写入的消息存储为一个list,以便测试。
public class TestServerStreamWriter<T> : IServerStreamWriter<T>
{
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=""},
new Student{Name=""}
};
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<HelloRequest>,简单实现一个类用于测试。
该类通过直接将客户端要传入的数据通过IEnumable<T>参数传入,模拟客户端的流式请求多个数据。
public class TestStreamReader<T> : IAsyncStreamReader<T>
{
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=""},
new HelloRequest{Name=""},
};
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=""},
new HelloRequest{Name="abc"},
};
var stream = new TestStreamReader<HelloRequest>(data); Assert.ThrowsAsync<ArgumentException>(async () => await service.Sum(stream, scc));
}
总结
以上代码,通过对gRPC服务依赖的关键资源进行mock或简单实现,达到了单元测试的目的。
.net core grpc单元测试 - 服务器端的更多相关文章
- .NET Core ❤ gRPC
这篇内容主要来自Microsoft .NET团队程序经理Sourabh Shirhatti的博客文章:https://grpc.io/blog/grpc-on-dotnetcore/, .NET Co ...
- Asp.Net Core Grpc 入门实践
Grpc简介 gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架. 在 gRPC 中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,从而更轻松地创 ...
- .net core grpc consul 实现服务注册 服务发现 负载均衡(二)
在上一篇 .net core grpc 实现通信(一) 中,我们实现的grpc通信在.net core中的可行性,但要在微服务中真正使用,还缺少 服务注册,服务发现及负载均衡等,本篇我们将在 .net ...
- NetCore服务虚拟化01(集群组件Sodao.Core.Grpc)
一. 起始 去年.NetCore2.0的发布,公司决定新项目采用.NetCore开发,当作试验.但是问题在于当前公司内部使用的RPC服务为Thrift v0.9 + zookeeper版本,经过个性化 ...
- ASP.NET Core gRPC 入门全家桶
一. 说明 本全家桶现在只包含了入门级别的资料,实战资料更新中. 二.官方文档 gRPC in Asp.Net Core :官方文档 gRPC 官网:点我跳转 三.入门全家桶 正片: ASP.NET ...
- ASP.NET Core gRPC 健康检查的实现方式
一. 前言 gRPC 服务实现健康检查有两种方式,前面在此文 ASP.NET Core gRPC 使用 Consul 服务注册发现 中有提到过,这里归纳整理一下.gRPC 的健康检查,官方是定义了标准 ...
- .Net Core Grpc Consul 实现服务注册 服务发现 负载均衡
本文是基于..net core grpc consul 实现服务注册 服务发现 负载均衡(二)的,很多内容是直接复制过来的,..net core grpc consul 实现服务注册 服务发现 负载均 ...
- .Net Core Grpc 实现通信
.Net Core 3.0已经把Grpc作为一个默认的模板引入,所以我认为每一个.Net程序员都有学习Grpc的必要,当然这不是必须的. 我在我的前一篇文章中介绍并创建了一个.Net Core 3.0 ...
- 旧 WCF 项目迁移到 asp.net core + gRPC 的尝试
一个月前,公司的运行WCF的windows服务器down掉了,由于 AWS 没有通知,没有能第一时间发现问题. 所以,客户提出将WCF服务由C#改为JAVA,在Linux上面运行:一方面,AWS对Li ...
随机推荐
- 基于Lua的游戏服务端框架简介
基于Lua的游戏服务端框架简介 [转]https://gameinstitute.qq.com/community/detail/106396 基于lua的游戏服务端框架简介 1. 引言 笔者目前在参 ...
- 详解JS闭包概念
闭包理解 1. 如何产生闭包? *当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,产生闭包 2. 闭包到底是什么? * 使用Chrome调试查看 * 理解一 ...
- [linux][mysql] MySQL中information_schema是什么
来源:MySQL中information_schema是什么 information_schema数据库是MySQL自带的,information_schema提供了访问数据库元数据的方式.这就是?元 ...
- async 和 await 例子
/// <summary> /// C# 5.0 /// .net framework4.5 /// CLR4.0 /// 引入了async 和 await.这两个关键字可以让你更方便的写 ...
- VSCode六大通用插件真香合集
目录 一.background:设置心水背景图 安利理由: 安装及设置步骤: 设置过程中使用的代码: 成果展示: 注意: 二.Material Theme(VSCode主题)+Material Ico ...
- Ansible playbook 编程
Ansible playbook 编程详解与各种小案例 主机规划 添加用户账号 说明: 1. 运维人员使用的登录账号: 2. 所有的业务都放在 /app/ 下「yun用户的家目录」,避免业务数据乱放: ...
- /sbin/mount.vboxsf: mounting failed with the error: Protocol error
公司换了新电脑,需要把之前的虚拟机的配置全部备份下来,在移动的过程中挂载共享文件夹时候出现了 /sbin/mount.vboxsf: mounting failed with the error: P ...
- failed to open stream :HTTP request failed 解决方法
用curl抓取,不要用file_get_contents(); 前者比后者效率高一点
- SQLI-LABS学习笔记(一)
逼话少说,如有错误,烦请指出,谢谢. 第一关 提示传个id的参数 后面跟个单引号 http://10.2.10.31/sqli/Less-1/?id=1’ 发现报错,这里看到是已经闭合了 You ha ...
- QIntValidator没有最小值的限制,继承然后写个新类来控制最小值
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/firecityplans/article ...