【质量文化第三弹】经典推荐:Table-Driven Unit Tests

在软件开发领域,有个DRY原则:Don't Repeat Yourself。注重简单高效的 Go提供了Table-Driven Unit Tests的编程模式,使用表格式在单个测试函数中定义多个输入和预期输出组合,而不是编写单个测试用例。通过这种方式,开发人员可以测试各种场景和边缘情况,并将代码重复率降到最低。本篇旨在通过示例分享Table-Driven Unit Tests写法,并与单个用例写法进行对比,让大家在写单测时能够选择更优方式。

table-Driven写法
go 复制代码
func TestRegexps(t *testing.T) {
	re := regexp.MustCompile(`text`)
	cases := map[string]struct { //注:这里的cases定义的结构是map[string]struct,好处是1.若case占用屏幕过长,可以折叠;2.执行时可以暴露具体失败的位置
		give     string
		expected bool
	}{
		"plus-signs":           {"+text+", true},
		"multiline-plus-signs": {"+text\nline2\nline3+", true},
		"money":                {"$text$", true},
		"multiline-money":      {"$text\nmulutple\nline$", true},
		"quotes":               {"pass:quotes[text]", true},
		"multiline-quotes":     {"pass:quotes[text\nline2\nline3]", true},
	}

	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			result := re.MatchString(tc.give)
			assert.Equal(t, tc.expected, result)
		})
	}
}

若是case执行失败,可以看到具体失败的位置,如下:

当失败case修复完后 ,也可以指定某个具体case重跑,如go test -run="TestRegexps/money"

非table-driven写法
go 复制代码
func TestRegexps(t *testing.T) {
   re := regexp.MustCompile(`text`)
   Convey("Given the regexp `text`", t, func() {
      Convey("When testing various cases", func() {
         give := "+text+"
         expected := true
         result := re.MatchString(give)
         Convey("It should match for plus signs", func() {
            So(result, ShouldEqual, expected)
         })
         give = "+text\nline2\nline3+"
         expected = true
         result = re.MatchString(give)
         Convey("It should match for multiline plus signs", func() {
            So(result, ShouldEqual, expected)
         })
         give = "$text$"
         expected = true
         result = re.MatchString(give)
         Convey("It should match for money signs", func() {
            So(result, ShouldEqual, expected)
         })
         give = "$text\nmulutple\nline$"
         expected = true
         result = re.MatchString(give)
         Convey("It should match for multiline money signs", func() {
            So(result, ShouldEqual, expected)
         })
         give = `pass:quotes[text]`
         expected = true
         result = re.MatchString(give)
         Convey("It should match for quotes", func() {
            So(result, ShouldEqual, expected)
         })
         give = "pass:quotes[text\nline2\nline3]"
         expected = true
         result = re.MatchString(give)
         Convey("It should match for multiline quotes", func() {
            So(result, ShouldEqual, expected)
         })
      })
   })
}

如上对比,可以看出非Table-driven方式写起来篇幅更长,且比较容易遗漏导致场景覆盖不全。

Table-Driven Unit Tests优势

简洁易维护: Table-Driven Unit Tests将测试用例定义和测试执行逻辑明确分开,有助于消除冗余代码,使测试代码更易于维护。

可扩展性: 随着代码库的增长,测试用例的数量也会增加,通过Table-Driven Unit Tests,可以轻松扩展。

覆盖范围:Table-Driven Unit Tests可确保全面覆盖不同的输入组合,这种方法有助于识别可能遗漏的边缘场景。

Table-Driven Unit Tests一些不适用场景

如果测试用例涉及复杂的设置、交互和依赖关系,或需要不同的预置条件,或需要自定义断言等,使用Table-Driven 写起来可能并不便利。

并且虽然Table-Driven能促进代码重用和简化,但是当table变得庞大复杂时,会降低测试代码的可读性和清晰度,违反测试代码的DAMP(Descriptive and Meaningful Phrases)原则。

总结

以上是我在公司宣传的单测系列的第三期内容,背景是在review开发工程师的单测时发现功能场景覆盖不全的情况大有发生,于是就有了这篇单测最佳实践的宣传,期望开发工程师在编写单测时能兼顾效率和场景覆盖。


后续的每一期内容我都会及时发布在掘金的工程师质量文化专栏,欢迎大家关注,共同进步。

最后,照例给大家看下我们在公司张贴的海报吧~

相关推荐
l***217820 小时前
Spring Boot 整合 log4j2 日志配置教程
spring boot·单元测试·log4j
川石课堂软件测试2 天前
Android和iOS APP平台测试的区别
android·数据库·ios·oracle·单元测试·测试用例·cocoa
大熊猫侯佩2 天前
Swift 6.2 列传(第十七篇):钟灵的“雷电蟒”与测试附件
单元测试·swift·apple
卓码软件测评2 天前
软件首版次认定测试机构:【Apifox与UMI框架结合:实现OpenAPI规范与Mock服务的自动化流水线】
测试工具·ci/cd·性能优化·单元测试·测试用例
虫小宝2 天前
企业微信API接口对接中Java后端的模拟测试(Mock)与单元测试实战技巧
java·单元测试·企业微信
黄金贼贼3 天前
2026最新java单元测试json校验器
java·单元测试·json
帅次3 天前
从开发到部署:软件实现、测试与交付全流程核心技术解析
功能测试·单元测试·测试用例·集成测试·压力测试·模块测试·安全性测试
叶落无痕526 天前
Electron应用自动化测试实例
前端·javascript·功能测试·测试工具·electron·单元测试
汽车仪器仪表相关领域6 天前
工况模拟精准检测,合规减排赋能行业 ——NHASM-1 型稳态工况法汽车排气检测系统项目实战经验分享
数据库·算法·单元测试·汽车·压力测试·可用性测试
码农水水6 天前
大疆Java面试被问:TCC事务的悬挂、空回滚问题解决方案
java·开发语言·人工智能·面试·职场和发展·单元测试·php