Golang单测试函数多断言疑问:为何未报错?是否为不良实践?
Golang测试代码疑问解答
先贴出测试代码:
func Test_HandlerError_EdpointsReturnError(t *testing.T) { assert := assert.New(t) objExpected := BodyForTest{ID: 2} endpointInternalErr := func(w http.ResponseWriter, r *http.Request) (interface{}, int, error) { return nil, 0, internalerrors.ErrInternal } endpointDomainErr := func(w http.ResponseWriter, r *http.Request) (interface{}, int, error) { return nil, 0, errors.New("Domain Error") } endpointObjAndStatus := func(w http.ResponseWriter, r *http.Request) (interface{}, int, error) { return objExpected, 201, nil } handlerFuncToInternalErr := HandlerError(endpointInternalErr) handlerFuncToDomainErr := HandlerError(endpointDomainErr) handlerFuncToObjAndStatus := HandlerError(endpointObjAndStatus) req, _ := http.NewRequest("GET", "/", nil) res := httptest.NewRecorder() handlerFuncToInternalErr.ServeHTTP(res, req) handlerFuncToDomainErr.ServeHTTP(res, req) handlerFuncToObjAndStatus.ServeHTTP(res, req) if http.StatusInternalServerError == res.Code { // INTERNAL ERROR assert.Equal(http.StatusInternalServerError, res.Code) assert.Contains(res.Body.String(), internalerrors.ErrInternal.Error()) } if http.StatusBadRequest == res.Code { // DOMAIN ERROR assert.Equal(http.StatusBadRequest, res.Code) assert.Contains(res.Body.String(), "Domain Error") } if http.StatusCreated == res.Code { assert.Equal(http.StatusCreated, res.Code) objReturned := BodyForTest{} json.Unmarshal(res.Body.Bytes(), &objReturned) assert.Equal(objExpected, objReturned) } }
问题1:同一个res变量多次调用ServeHTTP后,为何所有断言都能通过?
这是个误解——前两个if块里的断言根本没执行。httptest.NewRecorder返回的实例,每次调用ServeHTTP时会覆盖内部的Code、Body等核心字段。你的代码里连续三次调用:
- 第一次调用后,res.Code是500,Body存内部错误信息;
- 第二次调用覆盖res.Code为400,Body替换为域名错误内容;
- 第三次调用覆盖res.Code为201,Body替换为预期JSON数据。
到断言阶段时,res.Code已经是201,前两个if的条件不成立,里面的断言代码完全没运行,只有最后一个if块的断言执行并通过。你误以为所有断言都通过,实际上前两个测试场景的验证逻辑根本没触发。
问题2:单函数放多测试用例、用if包裹断言是否为不良编码实践?
绝对是不良实践,核心违反了测试用例的原子性和独立性原则:
- 问题定位困难:某个场景失败时,无法直接从测试结果判断是哪个分支出问题,必须额外调试;
- 共享变量污染:多个场景复用同一个res、req,前面的操作会覆盖变量状态,导致后续测试依赖前置结果,一旦前置逻辑变化,所有后续测试都会受影响;
- 测试覆盖不全:像你当前代码,前两个场景的断言没执行,等于没测,但你可能误以为覆盖了所有场景;
- 不符合Go测试规范:Go官方推荐用
t.Run拆分独立子测试,每个子测试有自己的上下文和变量,运行时可单独执行某个子测试,失败时能明确标识问题用例。
问题3:原用else if仅执行首个匹配分支,改多if后可运行的原因是什么?
用else if时,只有第一个满足条件的分支会执行,但你的代码三次调用ServeHTTP后,res.Code已经是201,所以只有最后一个else if分支执行,前两个分支的断言同样不会运行;改成多if后,逻辑结果完全一致——前两个if条件不成立,只有最后一个if执行。你觉得“可运行”,本质是前两个测试场景的验证逻辑根本没触发,并不是真的验证了这两个场景的正确性。
正确的做法是用t.Run拆分三个独立子测试,每个子测试创建自己的Recorder和请求,确保每个场景的验证逻辑独立执行:
func Test_HandlerError_Endpoints(t *testing.T) { assert := assert.New(t) objExpected := BodyForTest{ID: 2} t.Run("InternalError", func(t *testing.T) { endpoint := func(w http.ResponseWriter, r *http.Request) (interface{}, int, error) { return nil, 0, internalerrors.ErrInternal } handler := HandlerError(endpoint) req, _ := http.NewRequest("GET", "/", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, req) assert.Equal(http.StatusInternalServerError, res.Code) assert.Contains(res.Body.String(), internalerrors.ErrInternal.Error()) }) t.Run("DomainError", func(t *testing.T) { endpoint := func(w http.ResponseWriter, r *http.Request) (interface{}, int, error) { return nil, 0, errors.New("Domain Error") } handler := HandlerError(endpoint) req, _ := http.NewRequest("GET", "/", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, req) assert.Equal(http.StatusBadRequest, res.Code) assert.Contains(res.Body.String(), "Domain Error") }) t.Run("ReturnObjAndStatus", func(t *testing.T) { endpoint := func(w http.ResponseWriter, r *http.Request) (interface{}, int, error) { return objExpected, 201, nil } handler := HandlerError(endpoint) req, _ := http.NewRequest("GET", "/", nil) res := httptest.NewRecorder() handler.ServeHTTP(res, req) assert.Equal(http.StatusCreated, res.Code) objReturned := BodyForTest{} json.Unmarshal(res.Body.Bytes(), &objReturned) assert.Equal(objExpected, objReturned) }) }
内容的提问来源于stack exchange,提问作者Vinícius Martins
相关产品推荐
相关产品推荐

