网站建设 电子商务 品牌首选IDC,地方网站域名选择,小程序模板免费制作,企智网站建设接着上一篇内容#xff0c;我们继续~
四、测试的目标之三#xff1a;快速反馈
测试的快速反馈有两个方面的含义: 1.测试运行要快速出结果。 2.当测试失败时#xff0c;要能快速定位失败原因。
测试运行效率决定了开发的工作周期运转的快慢。在理想的 TDD 模型中#x…接着上一篇内容我们继续~
四、测试的目标之三快速反馈
测试的快速反馈有两个方面的含义: 1.测试运行要快速出结果。 2.当测试失败时要能快速定位失败原因。
测试运行效率决定了开发的工作周期运转的快慢。在理想的 TDD 模型中开发人员一遍又一遍地重复着“测试 - 实现 - 测试“ 这样的周期循环直到所有用例通过。持续集成和持续交付的过程也是如此。不管是单元测试还是大型测试运行效率都是应该追求的目标。
同样在的道理当测试出现用例失败时如果我们要花很长的时间来定位到原因也会拖慢我们从“测试”到“实现”的速度。要提高快速定位的能力一方面要提高被测代码的可观测性另一方面要给用例合理命名还有在断言时加入有价值易读易懂的 message. 在去年的一次分享中已经提到过如何建设被测系统的可观测性并且做了一些支撑工具。在用例命名和断言信息方面越是大型测试要求越高因为其所覆盖的代码范围广失败节点多而在单测中则相对要求低一些因为其被测代码覆盖范围小相对容易定位。
但是在单元测试时依然要认真给用例命名充分添加断言信息。这一建议单独另起一段考虑的是当我们写单测时的心智问题。不少开发在写单测时多心智是“单测是写给我自己看的这个用例测试的是我负责的代码出了问题我很快就知道定位”但是从团队和业务的角度出发测试都是写给整个开发团队看的这与代码的 readability 是一样的。code for team, test for team! 测试用例怎样命名断言信息写些啥有很多博客和问答可以提供参考.这里提供一个建议在命名和添加断言信息时想象着它们在报告中是如何显示的。以下用一个实际的例子来对比一下断言信息好坏的明显区别。 用例 v1
func TestQueryRecentExecs(t *testing.T) {t.Run(count 100 is too large, func(t *testing.T) {proxy : clientProxy()ctx, cancelFunc : context.WithTimeout(context.Background(), time.Second*5)defer cancelFunc()req : caselog.QueryCaseRecentExecsRequest{CaseId: 5,Count: 100,}_, err : proxy.QueryCaseRecentExecs(ctx, req)require.Error(t, err)assert.Equal(t, errs.ErrorTypeBusiness, trpcErrType(err))})
}该用例中被测接口要求入参中
Count不能大于等于 100我们向被测服务发起异常参数的请求期望其返回业务类型的错误(不能返回框架类型的错误框架类型的错误是诸如服务寻址失败超时之类的跟业务无关的错误具体参考 trpc 错误手册)。某次运行用例失败后得到的 log 如下:
test log v1
FailedRUN TestQueryRecentExecs/count_100_is_too_largecaselog_test.go:56: Error Trace: caselog_test.go:56Error: Not equal: expected: 2actual : 1Test: TestQueryRecentExecs/count_100_is_too_large--- FAIL: TestQueryRecentExecs/count_100_is_too_large (2.00s)从这个错误信息中我们从用例名中知道目的是验证
Count值为 100 过大而产生错误但是在错误信息中我们读到的是“因为我们期望 2 而实际值为 1用例失败”。这里 2 和 1 分别是什么没有信息。
下面进行一版改进
用例 v2 func TestQueryRecentExecs(t *testing.T) {t.Run(large count 100 cause biz error, func(t *testing.T) {proxy : clientProxy()ctx, cancelFunc : context.WithTimeout(context.Background(), time.Second*5)defer cancelFunc()req : caselog.QueryCaseRecentExecsRequest{CaseId: 5,Count: 100,}_, err : proxy.QueryCaseRecentExecs(ctx, req)require.Error(t, err)assert.Equal(t, business, trpcErrTypeName(err), unexpected trpc error type)})
}test log v2
FailedRUN TestQueryRecentExecs/large_count_100_cause_biz_errorcaselog_test.go:80: Error Trace: caselog_test.go:80Error: Not equal: expected: businessactual : frameworkDiff:--- Expected Actual -1 1 -businessframeworkTest: TestQueryRecentExecs/large_count_100_cause_biz_errorMessages: unexpected trpc error type--- FAIL: TestQueryRecentExecs/large_count_100_cause_biz_error (0.81s)v2 中很明确trpc error type 与预期不符预期是 business 而实际值为 framework. 这是一个接口测试出现 Framework error 通常是被测服务没有正确部署或者测试流水线所在网络环境与被测服务不通。(用例中使用 testify 断言其断言格式固定可能不一定是最好的格式略显啰嗦但是它在其他方面比较方便为了整个项目统一我们只能“因地制宜”。更简洁的断言信息是
t.Errorf(QueryCaseRecentExecs response error type mismatch got %s, want %s, trpcErrTypeName(err), business)即使该用例不是我写的只要具备基本的 trpc 背景知识看到报告后第一反应就是去确认被测服务是否健康以及流水线网络环境是否正确而在 v1 中我可能还得打开 ide 查看一下用例代码看用例代码还只能知道不是 business 错误实际是什么错误并不知道还得跳转到 trpc 的源代码才知道 1 是 framework 错误。还有另一种错误是 “callee framework”。孰快孰慢一目了然。
五、测试的第四个目标用例集的可维护性 1.功能代码有可读性要求测试代码也有同样功能代码有可维护性要求测试代码也有可维护性要求。可维护性最佳实践与功能代码是相通的仅举几例: DRY, 以提取函数/提取常量来替代复制粘贴。 2.以配置代替写死值可以参考一些 go 的标准库里面单元测试的方法可以在测试时指定 flag在用例中 parseFlags 来读取配置。 3.不要滥用设计模式测试代码复杂度不宜过高否则我们是否还有给测试代码写测试代码 参考阅读 《Unit Testing: Principles, Practices, and Patterns》
脚注 [1]覆盖率并不一定是越高越好对于没有测试价值的低风险的代码强行覆盖会消耗过多的时间和精力。怎样定一个覆盖率红线是另一个话题长话短说就是代码库的 owner 自行决定才是最科学的。
[2]那些单元测试已经充分测试过的逻辑无需在接口测试和端到端测试中重复。 优测测试平台简介 是一个为企业与开发者提供专业的测试工具和服务的平台沉淀十年产品测试经验提供终端测试、接口测试、性能测试、安全测试等多领域测试服务与产品协助客户提高效率降低成本保证产品质量。