一. 使用httptest来mock一些http的服务
1.1 背景说明
最近做了一个营销活动,开发礼包领取功能时依赖了外部的系统发放点券,已知点券发放接口是非常稳定ok的了,目前这个接口仅可以在生产被调用。为了测试礼包领取功能,需要mock掉依赖的外部点券发放接口。
1.2 操作实践
代码语言:txt复制// 这是配置文件里面的,为了演示这里写死
// RemoteGamePointsApi 点券api地址
var RemoteGamePointsApi = "/gift_pack"
// RemoteGamePointsServiceAddress 点券服务地址
var RemoteGamePointsServiceAddress = ""
// GiftPackResp 外部点券发放接口响应
type GiftPackResp struct {
QQ string
GamePoints int64
}
// GiftPackGet 礼包领取功能
func GiftPackGet(qq string) error {
// step1. 参数校验
// step2. 检查用户是否在本次活动黑名单 ...
// step3.检查用户是否已经领取过了 ...
// step4. 锁定当前账号, 防止同一个时刻同一个账号同时发放 ...
// step5. 调用点券发放接口
return sendGamePoints(qq)
}
// SendGamePoints 调用外部接口发送游戏点券
func sendGamePoints(qq string) error {
if !qqValidator(qq) {
return errors.New("qq is invalid")
}
// 发送点券
resp, err := http.Get(fmt.Sprintf("%s/%s?qq=%s", RemoteGamePointsServiceAddress, RemoteGamePointsApi, qq))
defer resp.Body.Close()
// 解析响应结果
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
ret := GiftPackResp{}
err = json.Unmarshal(bs, &ret)
if err != nil {
return err
}
fmt.Println(ret)
return err
}
// qq 合法性校验
func qqValidator(qq string) bool {
reg := regexp.MustCompile(`^[1-9]d{3,10}$`)
return reg.MatchString(qq)
}
// Test_GiftPackGet 测试礼包发放接口
func Test_GiftPackGet(t *testing.T) {
Convey("test utils GiftPackGet", t, func() {
Convey("When request use httptest mock remote api", func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusNotFound)
}
if r.URL.EscapedPath() != RemoteGamePointsApi {
w.WriteHeader(http.StatusNotFound)
}
qq := r.URL.Query().Get("qq")
w.WriteHeader(http.StatusOK)
g := GiftPackResp{qq, 100}
bs, _ := json.Marshal(g)
w.Write(bs)
}))
defer ts.Close()
RemoteGamePointsServiceAddress = ts.URL
err := GiftPackGet("3095764312")
So(err, ShouldBeNil)
})
})
}
代码解释:
- GiftPackGet 是对外的礼包发放接口,在调用前需要做一些活动规则的检查
- sendGamePoints 是调用的外部的点券发送接口api,调用会返回点券发送的回执
- 通过mock掉点券发送接口api, 我们可以将测试聚焦在具体的活动规则检查上,这部分才是我们自己的业务逻辑,是我们需要重点测试的部分。
二. 关于mock的思考
关于mock一直都是一个讨论比较激烈的点:
一方的人主张不要滥用mock,能不mock就不mock。被测单元也不一定是具体的一个函数,可能是多个函数本来就应该串起来,必要的时候再mock。
一方则主张将被测函数所有调用的外面函数全部mock掉,只关注被测函数自己的一行行代码,只要调用其他函数,全都mock掉,用假数据来测试。
个人感觉用不用mock就是一个选择问题,需要具体问题具体看待。如果依赖的模块自己可控, 那么我宁愿用测试数据保证上下依赖ok,专注到被测试的模块。
三. 什么时候适合mock?
- 该对象提供非确定的结果(比如当前的时间或者当前的温度)。
- 对象的某些状态难以创建或者重现(比如网络错误或者文件读写错误)。
- 对象方法上的执行太慢(比如在测试开始之前初始化数据库)。
- 该对象还不存在或者其行为可能发生变化(比如测试驱动开发中驱动创建新的类)。
- 该对象必须包含一些专门为测试准备的数据或者方法。
- 外部的一些调用代价较高的接口(例如调用一次100元的接口)。