github.com/XiaoMi/Gaea@v1.2.5/util/mocks/pipeTest/README.md (about) 1 # 使用 PipeTest 去进行连接测试 2 3 > PipeTest 会创建一连串连接的来回发送和接收,要被测试的函数只要中途进行截取部份连接后,就可以继续进行之后的测试 4 5 ## PipeTest 测试简介 6 7 > 以下会说明 PipeTest 测试的 1 类 2 对象 3 时序 和 4 连接重置等说明 8 9 ### PipeTest 主要的类 DcMocker 10 11 类 DcMocker 为仿真连接用的对象,由连接 net.Conn 、缓存连接 bufio.Reader and bufio.Writer、读写完成等待 sync.WaitGroup 和 测试控制 testing.T,如下类图所示 12 13 ![PipeTest的类图](./assets/pipeTest的类图.png) 14 15 代码位于 Gaea/util/mocks/pipeTest/pipeTest.go,内容如下 16 17 ```go 18 // DcMocker 用来模拟数据库服务器的读取和回应的类 19 type DcMocker struct { 20 t *testing.T // 单元测试的类 21 bufReader *bufio.Reader // 有缓存的读取 (接收端) 22 bufWriter *bufio.Writer // 有缓存的写入 (传送端) 23 connRead net.Conn // pipe 的读取连线 (接收端) 24 connWrite net.Conn // pipe 的写入连线 (传送端) 25 wg *sync.WaitGroup // 在测试流程的操作边界等待 26 replyFunc ReplyFuncType // 设定相对应的回应函数 27 err error // 错误 28 } 29 ``` 30 31 ### 类 DcMocker 函数列表 32 33 > - 代码位于 Gaea/util/mocks/pipeTest/pipeTest.go 34 > - 以下内容可以使用以下命令去产生 35 > 36 > ```bash 37 > $ cd Gaea/util/mocks/pipeTest 38 > $ go doc -all 39 > # 之后会显示所有函数的内容 40 > ``` 41 42 ```go 43 func NewDcMocker(t *testing.T, connRead, connWrite net.Conn, reply ReplyFuncType) *DcMocker 44 ``` 45 46 NewDcMocker 产生新的直连 dc 模拟对象 47 48 ```go 49 func (dcM *DcMocker) GetBufReader() *bufio.Reader 50 ``` 51 52 GetBufReader 为获得直连 dc 模拟对象的缓存读取 53 54 ```go 55 func (dcM *DcMocker) GetBufWriter() *bufio.Writer 56 ``` 57 58 GetBufWriter 为获得直连 dc 模拟对象的缓存写入 59 60 ```go 61 func (dcM *DcMocker) GetConnRead() net.Conn 62 ``` 63 64 65 GetConnRead 为获得直连 dc 模拟对象的读取连线 66 67 ```go 68 func (dcM *DcMocker) GetConnWrite() net.Conn 69 ``` 70 71 72 GetConnWrite 为获得直连 dc 模拟对象的写入连线 73 74 ```go 75 func (dcM *DcMocker) OverwriteConnBufRead(connRead net.Conn, bufReader *bufio.Reader) error 76 ``` 77 78 79 OverwriteConnBufRead 为临时覆写取代直连 dc 模拟对象的 读取连线 connRead 或者是 缓存读取 bufReader 80 81 ```go 82 func (dcM *DcMocker) OverwriteConnBufWrite(connWrite net.Conn, bufWriter *bufio.Writer) error 83 ``` 84 85 86 OverwriteConnBufWrite 为临时覆写取代直连 dc 模拟对象的 写入连线 connWrite 或者是 缓存写入 bufWriter 87 88 ```go 89 func (dcM *DcMocker) Reply(otherSide *DcMocker) (msg []uint8) 90 ``` 91 92 93 Reply 为直连 dc 用来模拟回应数据,大部份接连 SendOrReceive 函数后执行 94 95 ```go 96 func (dcM *DcMocker) ResetDcMockers(otherSide *DcMocker) error 97 ``` 98 99 100 ResetDcMockers 为重置单一连线方向的直连 dc 模拟对象 (重置单一连线方向的方式在后面说明) 101 102 ```go 103 func (dcM *DcMocker) SendOrReceive(data []uint8) *DcMocker 104 ``` 105 106 SendOrReceive 为直连 dc 用来模拟接收或传入讯息, 107 108 比如客户端 "传送 Send" 讯息到服务端、客户端再 "接收 Receive" 服务端的回传讯息 109 110 ```go 111 func (dcM *DcMocker) WaitAndReset(otherSide *DcMocker) error 112 ``` 113 114 115 WaitAndReset 为直连 dc 用来等待在 Pipe 的整个数据读写操作完成 116 117 ```go 118 type ReplyFuncType func([]uint8) []uint8 119 ``` 120 121 122 ReplyFuncType 回应函数的型态,测试时,当客户端或服务端接收到讯息时,可以利用此函数去创建回传讯息 123 124 ### PipeTest 对象图 125 126 NewDcServerClient 函数会先使用产生 4 个连接 net.Conn,分别为 read0、write0、read1 和 write1, 127 128 其中 read0 和 write0 相通,read1 和 write1 相通, 129 130 所以会有以下状况 131 132 #### 状况一 mockClient 至 mockServer 133 134 当 mockClient 在 write1 写入要发送的数据,之后 mockServer 就可以在 read1 读取到接收的数据 135 136 #### 状况二 mockServer 至 mockClient 137 138 当 mockServer 在 write0 写入要发送的数据,之后 mockClient 就可以在 read0 读取到接收的数据 139 140 ![PipeTest的对象图](./assets/pipeTest的对象图.png) 141 142 ### 单一连线方向重置 143 144 当 Pipe 被写入 EOF 消息后,就会停止读取后面更多的数据,这时就要把 Pipe 进行重置,以方便后续使用 145 146 会有以下状况 147 148 #### 状况一 mockClient 至 mockServer 149 150 当 mockClient 发送的数据时,会经由 write1、Pipe2 和 read1,最后发送到 mockServer 151 152 这时 Pipe2 的 read1 和 write1 都被使用过,之后如果还要再被使用,就要进行重置,是为单一连线方向重置 153 154 #### 状况二 mockServer 至 mockClient 155 156 当 mockServer 发送的数据时,会经由 write0、Pipe 和 read0,最后发送到 mockClient 157 158 这时 Pipe 的 read0 和 write0 都被使用过,之后如果还要再被使用,就要进行重置,是为另一个单一连线方向重置 159 160 ![单一连线方向重置](./assets/单一连线方向重置.png) 161 162 ## PipeTest 测试的操作 163 164 > 以下提供多个例子,关于使用 PipeTest 进行连接测试,例子一 为来回连接五次的测试,是用来验证整个 PipeTest 运作是否正确,是为测试的验证,其余的就为实际操作例子 165 166 ### 例子一 使用来回五次连续进行验证 167 168 - 当 MockClient 和 MockServer 一连接到 Pipe 时,就可以进行后续测试 169 - 一个循环为先 "客户端发送消息到服务端" 再来 "服务端发送消息到客户端",以上操作连续循环 5 次 170 - 消息经过 reply() 函数,新消息的数值为原始消息加1,观察消息传递消息过程中,数值是否有正常添加,就可以验证整个测试流程是否正确 171 - 当等待整个 Pipe 读取和写入完成时,消息就传递到对方,对方就能进行接收和读取动作 172 173 ![PipeTest连续测试时序图](./assets/pipeTest连续测试时序图.png) 174 175 代码位于 Gaea/util/mocks/pipeTest/pipeTest_test.go,内容如下 176 177 ```go 178 // TestPipeTestWorkable 为验证测试 PipeTest 是否能正常运作,以下测试不使用 MariaDB 的服务器,只是单纯的单元测试 179 func TestPipeTestWorkable(t *testing.T) { 180 t.Run("此为测试 PipeTest 的验证测试,主要是用来确认整个测试流程没有问题", func(t *testing.T) { 181 // 开始模拟对象 182 mockClient, mockServer := NewDcServerClient(t, TestReplyFunc) // 产生 mockClient 和 mockServer 模拟对象 183 184 // 产生一开始的讯息和之后的预期正确讯息 185 msg0 := []uint8{0} // 起始传送讯息 186 correct := uint8(0) // 之后的预期正确讯息 187 188 // 产生一连串的接收和回应的操作 189 for i := 0; i < 5; i++ { 190 msg1 := mockClient.SendOrReceive(msg0).Reply(mockServer) // 接收和回应 191 correct++ // 每经过一个reply() 函数时,回应讯息会加1 192 require.Equal(t, msg1[0], correct) 193 msg0 = mockServer.SendOrReceive(msg1).Reply(mockClient) // 接收和回应 194 correct++ // 每经过一个reply() 函数时,回应讯息会加1 195 require.Equal(t, msg0[0], correct) 196 } 197 }) 198 } 199 ``` 200 201 ### 例子二 临时函数介入 202 203 - 整个测试流程不变,但是在中途要 "被测试的函数" 临时接入 Pipe,就可以仿真对方发送过来的消息,再仿真进行回应 204 205 ![TestPipe测试临时函数介入时序图](./assets/pipeTest测试临时函数介入时序图.png) 206 207 代码位于 Gaea/backend/direct_connection_test.go,内容如下 208 209 ```go 210 // TestDirectConnWithoutDB 为测试数据库的后端连线流程,以下测试不使用 MariaDB 的服务器,只是单纯的单元测试 211 func TestDirectConnWithoutDB(t *testing.T) { 212 // 开始正式测试 213 t.Run("测试数据库后端连线的初始交握", func(t *testing.T) { 214 // 开始模拟 215 mockGaea, mockMariaDB := pipeTest.NewDcServerClient(t, pipeTest.TestReplyFunc) // 产生 Gaea 和 mockServer 模拟对象 216 mockGaea.SendOrReceive(mysqlInitHandShakeResponse) // 模拟数据库开始交握 217 218 // 产生 Mysql dc 直连对象 (用以下内容取代 reply() 函数 !) 219 var dc DirectConnection 220 var mysqlConn = mysql.NewConn(mockMariaDB.GetConnRead()) 221 dc.conn = mysqlConn 222 err := dc.readInitialHandshake() 223 require.Equal(t, err, nil) 224 225 // 等待和确认资料已经写入 pipe 并单方向重置模拟对象 226 err = mockGaea.WaitAndReset(mockMariaDB) 227 require.Equal(t, err, nil) 228 229 // 计算后的检查 230 require.Equal(t, dc.capability, uint32(2181036030)) // 检查功能标志 capability 231 require.Equal(t, dc.conn.ConnectionID, uint32(16)) // 检查连线编号 connection id 232 require.Equal(t, dc.salt, []uint8{81, 64, 43, 85, 76, 90, 97, 91, 34, 53, 36, 85, 93, 86, 117, 105, 49, 87, 65, 125}) // 检查 Salt 233 require.Equal(t, dc.status, mysql.ServerStatusAutocommit) // 检查服务器状态 234 }) 235 } 236 ``` 237 238 ### 例子三 测试函数抽换缓存 239 240 当 mockClient 和 mockServer 接通 Pipe 时,立刻使用 OverwriteBufConn 函数抽换缓存写入,并用 Reset 重置写入连接,之后的仿真过程不变 241 242 ![PipeTest测试函数抽换缓存时序图](./assets/PipeTest测试函数抽换缓存时序图.png) 243 244 代码位于 Gaea/mysql/conn_test.go,内容如下 245 246 ```go 247 // TestMariadbConnWithoutDB 为用来测试数据库一开始连线的详细流程,以下测试不使用 MariaDB 的服务器,只是单纯的单元测试 248 func TestMariadbConnWithoutDB(t *testing.T) { 249 // 函数测试开始 250 t.Run("MariaDB连接 的抽换缓存测试", func(t *testing.T) { 251 // 开始模拟 252 mockClient, mockServer := pipeTest.NewDcServerClient(t, pipeTest.TestReplyFunc) // 产生 Gaea 和 MariaDB 模拟对象 253 254 // 针对这次测试进行临时修改 255 err := mockClient.OverwriteConnBufWrite(nil, writersPool.Get().(*bufio.Writer)) 256 mockClient.GetBufWriter().Reset(mockClient.GetConnWrite()) 257 require.Equal(t, err, nil) 258 259 // 产生一开始的讯息和预期讯息 260 msg0 := []uint8{0} // 起始传送讯息 261 correct := uint8(0) // 预期之后的正确讯息 262 263 // 开始进行讯息操作 264 265 // 写入部份 266 mockClient.SendOrReceive(msg0) // 模拟客户端传送讯息 267 require.Equal(t, msg0[0], correct) 268 269 // 读取部份 270 msg1 := mockClient.Reply(mockServer) // 模拟服务端接收讯息 271 correct++ 272 require.Equal(t, msg1[0], correct) 273 }) 274 } 275 ``` 276