您好,我有一个简单的应用程序,它有一种 gRPC 方法,它按预期工作,但我不知道如何正确地集成测试它。(我也是生锈新手)。即我想调用 gRPC add_merchant 方法并检查响应是否包含正确的值。
我有以下结构:
app
proto
merchant.proto
src
main.rs
merchant.rs
tests
merchant_test.rs
build.rs
Cargo.toml
Run Code Online (Sandbox Code Playgroud)
商家.proto
syntax = "proto3";
package merchant;
service MerchantService {
rpc AddMerchant (Merchant) returns (Merchant);
}
message Merchant {
string name = 1;
}
Run Code Online (Sandbox Code Playgroud)
商人.rs
mod merchant;
use merchant::merchant_service_server::MerchantServiceServer;
use merchant::MerchantServiceImpl;
use tonic::transport::Server;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:50051".parse()?;
let merchant = MerchantServiceImpl::default();
Server::builder()
.add_service(MerchantServiceServer::new(merchant))
.serve(addr)
.await?;
Ok(())
}
Run Code Online (Sandbox Code Playgroud)
主程序.rs
use tonic::{Request, Response, Status};
use crate::merchant::merchant_service_server::MerchantService;
tonic::include_proto!("merchant");
#[derive(Debug, Default)]
pub struct MerchantServiceImpl {
}
#[tonic::async_trait]
impl MerchantService for MerchantServiceImpl {
async fn add_merchant(&self, request: Request<Merchant>) ->
Result<Response<Merchant>, Status> {
let response = Merchant {
name: "name".to_string()
};
Ok(Response::new(response))
}
}
Run Code Online (Sandbox Code Playgroud)
merchant_test.rs 应该是什么样子?
Fra*_*zzi 10
我几乎花了 6 个小时尝试用 tonic 做同样的事情,并提出了使用 unix 域套接字来对 gRPC 方法的实现进行单元测试的想法。tonic-example 仓库有一个uds文件夹可以帮助我们。
老实说,我自己没有找到解决方案,这是团队的努力。以下是如何使用 future 测试 tonic gRPC 服务(详细信息如下)。
在Cargo.toml添加
[dev-dependencies]
tokio-stream = { version = "0.1.8", features = ["net"] }
tower = { version = "0.4" }
tempfile = "3.3.0"
Run Code Online (Sandbox Code Playgroud)
那么内容tests.rs就是
use std::future::Future;
use std::sync::Arc;
use tempfile::NamedTempFile;
use tokio::net::{UnixListener, UnixStream};
use tokio_stream::wrappers::UnixListenerStream;
use tonic::transport::{Channel, Endpoint, Server, Uri};
use tonic::{Request, Response, Status};
use tower::service_fn;
struct ServerStub {}
#[tonic::async_trait]
impl MerchantService for ServerStub {
async fn add_merchant(&self, _request: Request<Merchant>)) -> Result<Response<Merchant>, Status> {
// Stub your response
return Ok(Response::new(Merchant {
name: "name".to_string()
};));
}
}
async fn server_and_client_stub() -> (impl Future<Output = ()>, MerchantServiceClient<Channel>) {
let socket = NamedTempFile::new().unwrap();
let socket = Arc::new(socket.into_temp_path());
std::fs::remove_file(&*socket).unwrap();
let uds = UnixListener::bind(&*socket).unwrap();
let stream = UnixListenerStream::new(uds);
let serve_future = async {
let result = Server::builder()
.add_service(MerchantServiceServer::new(ServerStub {}))
.serve_with_incoming(stream)
.await;
// Server must be running fine...
assert!(result.is_ok());
};
let socket = Arc::clone(&socket);
// Connect to the server over a Unix socket
// The URL will be ignored.
let channel = Endpoint::try_from("http://any.url")
.unwrap()
.connect_with_connector(service_fn(move |_: Uri| {
let socket = Arc::clone(&socket);
async move { UnixStream::connect(&*socket).await }
}))
.await
.unwrap();
let client = MerchantServiceClient::new(channel);
(serve_future, client)
}
// The actual test is here
#[tokio::test]
async fn add_merchant_test() {
let (serve_future, mut client) = server_and_client_stub().await;
let request_future = async {
let response = client
.add_merchant(Request::new(Merchant{
// Stub your request here
}))
.await
.unwrap()
.into_inner();
// Validate server response with assertions
assert_eq!(response.name,"name".to_string());
};
// Wait for completion, when the client request future completes
tokio::select! {
_ = serve_future => panic!("server returned first"),
_ = request_future => (),
}
}
Run Code Online (Sandbox Code Playgroud)
为什么使用 Unix 套接字?并行运行测试并避免多个 TCP 服务器上的端口冲突更加容易。我们使用 TCP 进行了测试,它也能正常工作,但需要注意的是您需要随机化端点端口。
该server_and_client_stub函数启动 gRPC 服务器侦听安装在临时文件中的 UDS。它还使用相同的套接字连接地址构建客户端。该函数可以在所有测试中重复使用。
然后在 gRPC 方法的实际单元测试中,我们启动客户端,接收响应并进行断言。最后,我们等待其中一个 future(服务器/客户端)完成。
我认为这是众多可能的解决方案之一,但它对于我们的用例来说效果很好,希望它对您也有帮助。