Golang中如何进行消费者驱动的契约测试
Golang中如何进行消费者驱动的契约测试 大家好
我编写了一个基于REST的微服务,并希望对其进行测试。
我曾在网上读到,对于微服务测试,推荐使用消费者驱动契约测试(CDCT)方法。
如何在不使用任何框架的情况下,用Go语言编写CDCT?
谢谢
3 回复
关于单元测试HTTP服务器,我推荐这个视频作为入门指南:https://youtu.be/hVFEV-ieeew
更多关于Golang中如何进行消费者驱动的契约测试的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
消费者驱动的契约测试仅意味着您服务的API,即“契约”,是由客户端而非服务器定义的。这不是技术问题,而是组织问题。
您可以使用 httptest.Server 来测试任何用Go实现的REST服务器。
在Go中实现消费者驱动的契约测试(CDCT)可以通过标准库和自定义代码完成。以下是一个不使用外部框架的示例方案:
1. 定义契约结构
// contract.go
package cdc
type Contract struct {
Consumer string `json:"consumer"`
Provider string `json:"provider"`
Request RequestDefinition `json:"request"`
Response ResponseDefinition `json:"response"`
}
type RequestDefinition struct {
Method string `json:"method"`
Path string `json:"path"`
Headers map[string]string `json:"headers"`
Body interface{} `json:"body"`
}
type ResponseDefinition struct {
StatusCode int `json:"statusCode"`
Headers map[string]string `json:"headers"`
Body map[string]interface{} `json:"body"`
}
2. 消费者端生成契约
// consumer_test.go
package consumer
import (
"encoding/json"
"io/ioutil"
"net/http"
"testing"
"yourmodule/cdc"
)
func TestUserServiceContract(t *testing.T) {
// 模拟消费者调用
resp, err := http.Get("http://localhost:8080/users/123")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var responseBody map[string]interface{}
json.Unmarshal(body, &responseBody)
// 生成契约
contract := cdc.Contract{
Consumer: "OrderService",
Provider: "UserService",
Request: cdc.RequestDefinition{
Method: "GET",
Path: "/users/{id}",
},
Response: cdc.ResponseDefinition{
StatusCode: 200,
Body: map[string]interface{}{
"id": "123",
"name": "John Doe",
"type": "premium",
},
},
}
// 保存契约文件
contractJSON, _ := json.MarshalIndent(contract, "", " ")
ioutil.WriteFile("contracts/user_service.json", contractJSON, 0644)
}
3. 提供者端验证契约
// provider_test.go
package provider
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"yourmodule/cdc"
)
func TestContractCompliance(t *testing.T) {
// 加载契约文件
data, err := ioutil.ReadFile("contracts/user_service.json")
if err != nil {
t.Fatal(err)
}
var contract cdc.Contract
json.Unmarshal(data, &contract)
// 启动测试服务器
srv := httptest.NewServer(yourActualHandler())
defer srv.Close()
// 执行契约中的请求
req, _ := http.NewRequest(
contract.Request.Method,
srv.URL+contract.Request.Path,
nil,
)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
// 验证状态码
if resp.StatusCode != contract.Response.StatusCode {
t.Errorf("Expected status %d, got %d",
contract.Response.StatusCode, resp.StatusCode)
}
// 验证响应体结构
var body map[string]interface{}
json.NewDecoder(resp.Body).Decode(&body)
for key, expectedValue := range contract.Response.Body {
if actualValue, exists := body[key]; !exists {
t.Errorf("Missing field: %s", key)
} else if actualValue != expectedValue {
t.Errorf("Field %s: expected %v, got %v",
key, expectedValue, actualValue)
}
}
}
4. 契约匹配器(可选增强)
// matcher.go
package cdc
import (
"reflect"
"regexp"
)
type Matcher struct {
Patterns map[string]string
}
func (m *Matcher) Match(expected, actual map[string]interface{}) bool {
for key, expVal := range expected {
actVal, exists := actual[key]
if !exists {
return false
}
if pattern, ok := m.Patterns[key]; ok {
if strVal, ok := actVal.(string); ok {
matched, _ := regexp.MatchString(pattern, strVal)
if !matched {
return false
}
}
} else if !reflect.DeepEqual(expVal, actVal) {
return false
}
}
return true
}
使用示例:
- 消费者运行测试生成契约文件
- 将契约文件提交到共享仓库
- 提供者定期运行契约验证测试
- 构建流水线中集成契约验证
这个方案提供了基本的CDCT功能,包括契约定义、生成和验证。可以通过添加字段类型验证、正则匹配、头信息验证等来扩展功能。

