学会使用表驱动编程模式后,领导了终于合了我的PR

1,155 阅读5分钟

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

引子

刚入行时,对测试驱动开发(TDD)还没有任何概念, 写代码也时直接梭哈到底(Leetcode 刷题除外)。那会,领导那我写一个时间操作相关的工具方法并提到要写好测试用例,如获取指定时间当天的0点和23点59分59秒之类的。我心想这么简单,直接梭哈上手并写好测试用例,直接提了PR,没想到第一次PR就被打回了。

为啥呢?其中一个获取零点时间的测试用例我是这样子写的:

//GetZeroTimeOfDay 获取目标时间的零点时间
func GetZeroTimeOfDay(t time.Time) time.Time {
	return time.Date(t.Year(), t.Month(), t.Day(),  0, 0, 0, 0, t.Location())
}


func parseTime(timeStr string) (t time.Time, err error) {
	t, err = time.Parse("2006-01-02 15:04:05", timeStr)
	return
}

func Test_GetZeroTime_HardCode(t *testing.T) {
	correctTime, err := parseTime("2021-11-28 00:00:00" )
	assert.Empty(t, err)
	t1, err := parseTime("2021-11-28 10:00:51")
	assert.Empty(t, err)
	assert.Equal(t, GetZeroTimeOfDay(t1), correctTime)
	t2, err := parseTime("2021-11-28 12:00:51")
	assert.Empty(t, err)
	assert.Equal(t, GetZeroTimeOfDay(t2), correctTime)
	t3, err := parseTime("2021-11-28 00:00:00")
	assert.Empty(t, err)
	assert.Equal(t, GetZeroTimeOfDay(t3), correctTime)
	t4, err := parseTime("2021-11-28 23:59:59")
	assert.Empty(t, err)
	assert.Equal(t, GetZeroTimeOfDay(t4), correctTime)
}

相信读者们都看出问题,这不是硬编码测试吗?这明显的问题也让领导指出了,并让我去了解一下表驱动这一编程模式, 对测试用例进行改造。

表驱动

什么是表驱动?

表驱动模式是一种(scheme)--从表里查找信息而不使用逻辑语句(if 和 case)。

引用自《代码大全2》 第18章

在《代码大全2》中举了一个简单的例子来说明表驱动编程模式实现方式。

如果我们需要编写一个方法,用来获取每个月的天数, 硬编码梭哈的话就会产生如下代码。

function GetDaysPerMonth(month) {
    if (month == 1) {
        return 31
    } else if (month == 2) {
        // 不考虑闰年
        return 28
    } else if (month == 3) {
        return 31
    } else if (month == 4) {
        return 30
    } else if (month == 5) {
        return 31
    }
    ....
}

使用表驱动编程模式的进行改成本质上就是使用一个哈希表/数组来存储条件和结果的映射关系。

改造后的代码如下所示, result数组的下标的含义是(月份数-1), 当然你也可以直接使用哈希表来存储月份和天书的映射关系。

const result = [31, 28, 31, 30, 31...]
function GetDaysPerMonth(month) {
    return result[month-1]
}

使用表驱动模式改造测试用例

根据表驱动编程模式的思想,我们需要对测试用例做如下改造:

  • 将所有需要测试数据集中保存到一个容器中哈希表或者数组皆可。
  • 设计一个测试骨架,读取测试数据并跑完所有定义好的测试用例

如以下代码所示, 我们定义好一个结构体, 其中ExpectTime表示正确的时间, TimeStr数组中保存我们需要测试的数据。

type TestCase struct {
	TimeStr []string
	ExpectTime string
}

定义好测试用例的结构之后,我们来编写测试骨架,其主要逻辑就是读取表中的数据, 并调用目标方法将取得的运算结果和表中定义的正确结果进行比较。

//Test_GetZeroTime_TableDriven  使用表驱动进行测试
func Test_GetZeroTime_TableDriven(t *testing.T) {
	testCases := []TestCase{
		{
			TimeStr: []string{
				"2021-11-28 10:00:51",
				"2021-11-28 12:00:51",
				"2021-11-28 00:00:00",
				"2021-11-28 23:59:59",
			},
			ExpectTime: "2021-11-28 00:00:00",
		},
	}
	for _, testCase := range testCases{
		expectTime, err := parseTime(testCase.ExpectTime)
		assert.Empty(t, err)
		for _, timeStr := range testCase.TimeStr {
			testTime, err := parseTime(timeStr)
			assert.Empty(t, err)
			result := GetZeroTimeOfDay(testTime)
			assert.Equal(t, result, expectTime)
		}
	}
}

输出结果如下所示: image.png

更进一步,从文件中读取测试数据

那么,还有进一步优化的空间吗?(这样子问,肯定是有)

虽然使用表驱动改造了测试用例, 但是要新增测试用例还是得在代码中进行修改, 那么如果我们将测试用例提取出来呢?然后直接读取测试用例文件得数据,之后再跑一遍测试用例,岂不美哉?

定义testdata.json文件用来存储测试用例得数据

{
  "ZeroTimeTestCase": [
    {
      "TimeStr": [
        "2021-11-28 10:00:51",
        "2021-11-28 12:00:51",
        "2021-11-28 00:00:00",
        "2021-11-28 23:59:59"
      ],
      "ExpectTime": "2021-11-28 00:00:00"
    },
    {
      "TimeStr": [
        "2021-01-28 10:11:51",
        "2021-01-28 12:00:51",
        "2021-01-28 17:00:00",
        "2021-01-28 23:59:57"
      ],
      "ExpectTime": "2021-01-28 00:00:00"
    }
  ]
}

改造后得测试用例代码如下所示:

type TestData struct{
	ZeroTimeTestCase []TestCase
}

func Test_GetZeroTime_ReadFile(t *testing.T) {
	bytes, err := os.ReadFile("./testdata.json")
	assert.Empty(t, err)
	data := &TestData{}
	err = json.Unmarshal(bytes, data)
	assert.Empty(t, err)
	for _, testCase := range data.ZeroTimeTestCase {
		expectTime, err := parseTime(testCase.ExpectTime)
		assert.Empty(t, err)
		for _, timeStr := range testCase.TimeStr {
			testTime, err := parseTime(timeStr)
			assert.Empty(t, err)
			result := GetZeroTimeOfDay(testTime)
			assert.Equal(t, result, expectTime)
		}
	}
}

注意: 读取测试文件时需要使用相对路径

运行所有测试用例, 输出如下所示:

image.png

总结

  • 使用表驱动编程模式来设计测试用例可以大幅度提升你编写测试用例得速度,并且减少硬编码, 此方法也同样适用于业务逻辑开发中。
  • 多编写测试用例, 有助于代码健康
  • 多阅读《代码大全2》有好处,虽然名字起得不咋地,但干货真的多

完整代码 -> 在这儿

如果本文对你有所帮助,欢迎点赞,关注,收藏, 谢谢各位老铁。

Golang文章推荐

代码规范相关: