前言

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 = ;
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

使用如下命令伪造service需要的logger:
var logger = Mock.Of<ILogger<GreeterService>>();

使用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单元测试 - 服务器端的更多相关文章

  1. .NET Core ❤ gRPC

    这篇内容主要来自Microsoft .NET团队程序经理Sourabh Shirhatti的博客文章:https://grpc.io/blog/grpc-on-dotnetcore/, .NET Co ...

  2. Asp.Net Core Grpc 入门实践

    Grpc简介 gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架. 在 gRPC 中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,从而更轻松地创 ...

  3. .net core grpc consul 实现服务注册 服务发现 负载均衡(二)

    在上一篇 .net core grpc 实现通信(一) 中,我们实现的grpc通信在.net core中的可行性,但要在微服务中真正使用,还缺少 服务注册,服务发现及负载均衡等,本篇我们将在 .net ...

  4. NetCore服务虚拟化01(集群组件Sodao.Core.Grpc)

    一. 起始 去年.NetCore2.0的发布,公司决定新项目采用.NetCore开发,当作试验.但是问题在于当前公司内部使用的RPC服务为Thrift v0.9 + zookeeper版本,经过个性化 ...

  5. ASP.NET Core gRPC 入门全家桶

    一. 说明 本全家桶现在只包含了入门级别的资料,实战资料更新中. 二.官方文档 gRPC in Asp.Net Core :官方文档 gRPC 官网:点我跳转 三.入门全家桶 正片: ASP.NET ...

  6. ASP.NET Core gRPC 健康检查的实现方式

    一. 前言 gRPC 服务实现健康检查有两种方式,前面在此文 ASP.NET Core gRPC 使用 Consul 服务注册发现 中有提到过,这里归纳整理一下.gRPC 的健康检查,官方是定义了标准 ...

  7. .Net Core Grpc Consul 实现服务注册 服务发现 负载均衡

    本文是基于..net core grpc consul 实现服务注册 服务发现 负载均衡(二)的,很多内容是直接复制过来的,..net core grpc consul 实现服务注册 服务发现 负载均 ...

  8. .Net Core Grpc 实现通信

    .Net Core 3.0已经把Grpc作为一个默认的模板引入,所以我认为每一个.Net程序员都有学习Grpc的必要,当然这不是必须的. 我在我的前一篇文章中介绍并创建了一个.Net Core 3.0 ...

  9. 旧 WCF 项目迁移到 asp.net core + gRPC 的尝试

    一个月前,公司的运行WCF的windows服务器down掉了,由于 AWS 没有通知,没有能第一时间发现问题. 所以,客户提出将WCF服务由C#改为JAVA,在Linux上面运行:一方面,AWS对Li ...

随机推荐

  1. vs 类型定义及语句,随机数

    1  类型定义: 1)小数: 类型         变量名           赋值 decimal       d   :              d=1.2m float             ...

  2. 操作google_sheets

    起源:最近了使用flask和bootstrap写了测试小工具,数据全部使用excel存储,部署到测试环境. 问题:每次每个人在使用excel数据时都需要重新编辑好的excel通过upload按钮传到服 ...

  3. Python神库分享之geoip2 IP定位库

    先安装这两个 pip install python-geoip-geolite2 -i https://pypi.douban.com/simple pip install geoip2 然后下载资源 ...

  4. Python开发基础之Python常用的数据类型

    一.Python介绍 Python是一种动态解释型的编程语言.Python它简单易学.功能强大.支持面向对象.函数式编程,可以在Windows.Linux等多种操作系统上使用,同时Python可以在J ...

  5. vue显示富文本

    来源:https://segmentfault.com/q/1010000013952512 用  v-html 属性解决

  6. Windows API 中 OVERLAPPED 结构体 初始化

    出处:https://github.com/microsoft/Windows-classic-samples/blob/1d363ff4bd17d8e20415b92e2ee989d615cc0d9 ...

  7. automake autoconf 使用详解

    本文地址: http://www.laruence.com/2009/11/18/1154.html 文章转自: http://www.linuxcomputer.cn/ 作为Linux下的程序开发人 ...

  8. vue2.x学习笔记(二十五)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12677019.html. 过滤器 vue允许开发者自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地 ...

  9. 使用3种协议搭建yum仓库

    制作本地yum仓库 开启服务一般要关闭防火墙,selinux之后再reboot ## 方案一:FTP协议------ftp://IP 下载vsftpd---启动vsftpd---ftp://10.0. ...

  10. IDC:企业需求疲软 第三季度全球服务器市场收入下滑7%

    根据IDC全球服务器季度追踪报告,2016年第三季度全球服务器市场同比减少7%至125亿美元.整个服务器市场的增长最近有所放缓,部分原因是超大规模数据中心增长放缓,以及受到高端服务器销售下滑的拖累.此 ...