.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

相关文章
|
11天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
2月前
|
网络协议 安全 测试技术
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
40 2
|
2月前
|
存储 弹性计算 运维
阿里云服务器ECS经济型e实例详细介绍_性能测试和租用价格
阿里云服务器ECS经济型e实例详细介绍_性能测试和租用价格,阿里云服务器ECS推出经济型e系列,经济型e实例是阿里云面向个人开发者、学生、小微企业,在中小型网站建设、开发测试、轻量级应用等场景推出的全新入门级云服务器,CPU采用Intel Xeon Platinum架构处理器,支持1:1、1:2、1:4多种处理器内存配比,e系列性价比优选
|
1月前
|
开发框架 人工智能 .NET
C#/.NET/.NET Core拾遗补漏合集(持续更新)
C#/.NET/.NET Core拾遗补漏合集(持续更新)
|
1月前
|
弹性计算 分布式计算 DataWorks
DataWorks报错问题之ecs自建数据库连通性测试报错如何解决
DataWorks是阿里云提供的一站式大数据开发与管理平台,支持数据集成、数据开发、数据治理等功能;在本汇总中,我们梳理了DataWorks产品在使用过程中经常遇到的问题及解答,以助用户在数据处理和分析工作中提高效率,降低难度。
|
1月前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
60 0
|
1月前
|
弹性计算 缓存 测试技术
阿里云2核4g服务器(费用价格/性能测试/支持人数)
阿里云2核4g服务器能支持多少人访问?2核4G服务器并发数性能测试,阿小云账号下的2核4G服务器支持20人同时在线访问,然而应用不同、类型不同、程序效率不同实际并发数也不同,2核4G服务器的在线访问人数取决于多个变量因素
|
1月前
|
弹性计算 缓存 测试技术
2核4g服务器能支持多少人访问?阿里云2核4G服务器并发数测试
2核4g服务器能支持多少人访问?阿里云2核4G服务器并发数测试,2核4G服务器并发数性能测试,阿小云账号下的2核4G服务器支持20人同时在线访问,然而应用不同、类型不同、程序效率不同实际并发数也不同,2核4G服务器的在线访问人数取决于多个变量因素
|
2月前
|
开发框架 前端开发 .NET
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
为了便于大家查找,特将之前开发的.Net Core相关的五大案例整理成文,共计440页,32w字,免费提供给大家,文章底部有PDF下载链接。
32 1
福利来袭,.NET Core开发5大案例,30w字PDF文档大放送!!!
|
2月前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报