初始化bufconn连接器
传统的单元测试,我们需要启动一个服务端,然后再启动一个客户端,首先需要占用我们的端口,其次资源开销还大。
因此我们可以使用google提供的bufconn
包,该包可以在内存上创建一个虚拟接口用于连接,资源消耗低,且不占用端口
同时由于他是运行在内存上的,因此你只能老实手动在单元测试内初始化他,而不能说跨进程进行测试
// 全局定义一个buff区域,该区域暂存整个GRPC请求req和Res,合理设置即可
const bufSize = 1024 * 1024
// 全局定一个bufListener 这样可以提高拨号器的利用率
var bufListener *bufconn.Listener
func init() {
bufListener := bufconn.Listen(bufSize)
s := grpc.NewServer()
gs := NewGRPCServer(addService{}) // 新建一个GRPC服务,该服务所属结构体必须实现了所有方法,否则会报错
pb.RegisterGRPCServerServer(s, gs) // 将GRPC服务注册
go func() { // 开一个线程用来让Server后台运行,避免阻塞
if err := s.Serve(bufListener); err != nil { // 将GRPC服务运行到bufconn上
log.Fatalf("Server init Server Error from bufConn :%s", err.Error())
}
}()
}
完成后,我们就在内存上定义了一个端点,可以对外提供服务,这个Server的句柄就在bufListener
客户端实现连接
完成服务端的启动后,就应该启动客户端了
与传统的GRPC客户端连接不同,服务端定义bufconn后,他就不会对外监听接口了,此时我们创建连接就需要使用grpc.WithContextDialer(bufDialer)
进行实现了,而其中的入参bufDialer 就是我么说的连接对象了
默认情况下,连接对象是通过target实现的,也就是直接采用
defaultDialOptions
这个拨号器,如果你调用了WithContextDialer,则会使用你自定义的拨号器对象
默认拨号器源码
func defaultDialOptions() dialOptions {
return dialOptions{
copts: transport.ConnectOptions{
ReadBufferSize: defaultReadBufSize,
WriteBufferSize: defaultWriteBufSize,
UseProxy: true,
UserAgent: grpcUA,
BufferPool: mem.DefaultBufferPool(),
},
bs: internalbackoff.DefaultExponential,
healthCheckFunc: internal.HealthCheckFunc,
idleTimeout: 30 * time.Minute,
defaultScheme: "dns",
maxCallAttempts: defaultMaxCallAttempts,
}
}
我们这里需要替换为bufconn的Dial,因此实现一个自定义函数,该函数用于返回一个net.Conn 对象,然后交给WithContextDialer 转换为一个option ,并最终交给NewClient
// 拨号器,不再直接指定端口拨号,而是采用bufListener拨号(因此我们的target也就失去了意义)
func bufDialer(ctx context.Context, target string) (net.Conn, error) {
return bufListener.Dial()
}
完成拨号器函数的编写后,就可以编写我们建立客户端连接部分了
func sum_test(){
conn, err := grpc.NewClient(
"passthrough:///bufnet", // 这里必须这么写
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(bufDialer)) // 这里实现了自定义拨号器的效果
if err != nil {
t.Errorf("Connect GRPC Server Error:%s", err.Error())
t.Fail()
}
defer conn.Close()
c := pb.NewGRPCServerClient(conn)
}
我们的Client初始化主要变化两点
- target变化为
passthrough:///bufnet
- 添加了
grpc.WithContextDialer(bufDialer)
:这里就是为了替换我们原有的Dial为bufconn,而替换方法就是调用我们拨号器创建函数,返回bufListener.Dial()这个net.Conn
对象
到此,我们就完成了客户端和服务端的连接了
调用测试和断言
调用测试就不必多言了,尝试看下效果就行,看会不会返回错误,以及结果
这里需要说一个包github.com/stretchr/testify
这个包是Golang流行的单元测试包,他可以提供:断言操作
Mock
测试接口
等操作
但是我们常用的就是断言操作了,他可以优化我们的代码长度
传统的代码测试
func TestSum(t *testing.T) {
t.Logf("Sum开始测试\n")
conn, err := grpc.NewClient(
"passthrough:///bufnet",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(bufDialer))
if err != nil {
t.Errorf("Connect GRPC Server Error:%s", err.Error())
t.Fail()
}
defer conn.Close()
c := pb.NewGRPCServerClient(conn)
// 测试相加方法
res, err := c.Sum(context.TODO(), &pb.SumRequest{
A: 3,
B: 5,
})
// 单独的断言测试
if err != nil {
fmt.Printf("Sum Error:%s\n", err.Error())
t.Fail()
} // 测试err是否存在
if res==nil{
fmt.Printf("Res is Nil")
t.Fail()
} // 测试res对象是否不存在
if res.V!=int64(8){
fmt.Printf("Res.V != 8")
t.Fail()
} // 测试返回值是否相同
t.Logf("Sum测试成功\n")
}
而调用了testify
后,我们可以使用语句快速断言了,节约了大量的IF
func TestSum(t *testing.T) {
t.Logf("Sum开始测试\n")
conn, err := grpc.NewClient(
"passthrough:///bufnet",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(bufDialer))
if err != nil {
t.Errorf("Connect GRPC Server Error:%s", err.Error())
t.Fail()
}
defer conn.Close()
c := pb.NewGRPCServerClient(conn)
// 测试相加方法
res, err := c.Sum(context.TODO(), &pb.SumRequest{
A: 3,
B: 5,
})
require.NoError(t, err) // 测试err是否存在
require.NotNil(t, res) // 测试res对象是否不存在
require.Equal(t, res.V, int64(8)) // 测试返回值是否相同,不相同则调用t对象下的error
t.Logf("Sum测试成功\n")
}
具体使用方式就在上面了,自己看就行
如果你需要看错误信息,则一定要使用NotError
这个方法去看,而不要使用Nil
方法去看,Nil方法不会返回错误信息,这点请注意
使用NoError
使用Nil