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