“This is the 12th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

primers

When I started out, I had no concept of test-driven development (TDD) and wrote code straight to the bottom (except for Leetcode brushes). At that time, I wrote a time manipulation tool method and mentioned writing test cases, such as getting the day of the specified time at 0 and 23:59:59 and so on. I thought it would be so easy, so I just started writing test cases and asked for PR, but the PR was knocked back the first time.

Why? One of the test cases that gets zero time is written like this:

//GetZeroTimeOfDay Gets the zero time of the target time
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("The 2006-01-02 15:04:05", timeStr)
	return
}

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

I’m sure readers see the problem. Isn’t this a hard-coded test? The obvious problem was pointed out by the leader, who asked me to take a look at the table-driven programming pattern and modify the test cases.

Table driven

What is table drive?

Table driven mode is a scheme that looks up information from tables without using logical statements (if and case).

Quote from Chapter 18 of Code Book 2

In Code 2, a simple example is given to illustrate the implementation of the table driven programming pattern.

If we needed to write a method to get the number of days in a month, hard-coding a boha would produce the following code.

function GetDaysPerMonth(month) {
    if (month == 1) {
        return 31
    } else if (month == 2) {
        // Leap years are not considered
        return 28
    } else if (month == 3) {
        return 31
    } else if (month == 4) {
        return 30
    } else if (month == 5) {
        return 31}... }Copy the code

Using table-driven programming is essentially using a hash table/array to store mappings between conditions and results.

The result array subscript is (number of months -1), but you can also use the hash table to store the mapping between the months and the book.

const result = [31.28.31.30.31...]
function GetDaysPerMonth(month) {
    return result[month-1]}Copy the code

Transform test cases using table driven patterns

According to the idea of table driven programming pattern, we need to make the following changes to the test case:

  • Store all data sets you want to test into a container, either a hash table or an array.
  • Design a test skeleton, read test data, and run through all defined test cases

As shown in the code below, we’ve defined a structure where ExpectTime represents the correct time and the TimeStr array holds the data we need to test.

type TestCase struct {
	TimeStr []string
	ExpectTime string
}
Copy the code

Once the structure of the test case is defined, we write the test skeleton, whose main logic is to read the data in the table and call the target method to compare the calculated result with the correct result defined in the table.

Test_GetZeroTime_TableDriven uses table drivers for testing
func Test_GetZeroTime_TableDriven(t *testing.T) {
	testCases := []TestCase{
		{
			TimeStr: []string{
				"The 2021-11-28 10:00:51"."The 2021-11-28 12:00:51"."The 2021-11-28 00:00:00"."The 2021-11-28 23:59:59",
			},
			ExpectTime: "The 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)
		}
	}
}
Copy the code

The following output is displayed:

Further, read the test data from the file

So is there room for further refinement? (There must be)

Although the test cases were modified with table drivers, the new test cases still had to be modified in the code, so what if we extracted the test cases? Wouldn’t it be nice to read the data directly from the test case file and then run the test case again?

Define testData. json files to store test case data

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

The modified test case code is as follows:

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)
		}
	}
}
Copy the code

Note: Relative paths are required to read the test file

Run all the test cases, and the output looks like this:

conclusion

  • Using the table-driven programming pattern to design test cases can greatly speed up the writing of test cases and reduce hard coding, and this approach is also applicable to business logic development.
  • Write more test cases to help code health
  • It’s good to read A lot of Code 2. It’s not a great title, but it’s a lot of stuff

The full code -> is here

If this article is helpful to you, welcome to like, pay attention to, favorites, thank you old iron.

Golang article recommended

Code specification related:

  • # What is cyclomatic complexity? See how the big guys implement code complexity checks?
  • The # Githook practice uses the code specification checking plugin golangci-lint as an example

Gin series

  • # Implementation principle of Gin middleware