译 | Prefer table driven tests(二)

简介: 译 | Prefer table driven tests(二)

Enumerating test cases

由于测试存储在 slice 中,我们可以在失败消息中打印出测试用例的索引:

func TestSplit(t *testing.T) {  
    tests := []struct {  
        input string  
        sep . string  
        want  []string  
    }{  
        {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},  
        {input: "a/b/c", sep: ",", want: []string{"a/b/c"}},  
        {input: "abc", sep: "/", want: []string{"abc"}},  
        {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},  
    }  
    for i, tc := range tests {  
        got := Split(tc.input, tc.sep)  
        if !reflect.DeepEqual(tc.want, got) {  
            t.Fatalf("**test %d:** expected: %v, got: %v", **i+1**, tc.want, got)  
        }  
    }  
}

现在,当我们运行 go test 我们得到了这个:

% go test  
--- FAIL: TestSplit (0.00s)  
    split_test.go:24: **test 4:** expected: [a b c], got: [a b c ]

这样好了一些。 现在我们知道第四个测试失败了,尽管我们不得不做了一点点捏造,因为 slice 索引和范围迭代是从 0 开始的。 这要求您的测试用例保持一致; 如果有些人从 0 开始报告而其他人使用 1 开始报告,那将会令人困惑。 并且,如果测试用例列表很长,则可能很难数大括号以确切地确定第4个测试用例由哪些结构构成。

Give your test cases names

另一种常见模式是在测试结构中包含名称字段。

func TestSplit(t *testing.T) {  
    tests := []struct {  
        **name  string**  
        input string  
        sep   string  
        want  []string  
    }{  
        {name: "simple", input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},  
        {name: "wrong sep", input: "a/b/c", sep: ",", want: []string{"a/b/c"}},  
        {name: "no sep", input: "abc", sep: "/", want: []string{"abc"}},  
        {name: "trailing sep", input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},  
    }  
    for _, tc := range tests {  
        got := Split(tc.input, tc.sep)  
        if !reflect.DeepEqual(tc.want, got) {  
            t.Fatalf("**%s:** expected: %v, got: %v", **tc.name**, tc.want, got)  
        }  
    }  
}

现在,当测试失败时,我们有一个描述性的名称,描述正在进行的测试。 我们不再需要尝试从输出中找出它 —— 现在还有一个字符串,我们可以搜索。

% go test 
--- FAIL: TestSplit (0.00s)  
    split_test.go:25: **trailing sep**: expected: [a b c], got: [a b c ]

我们可以使用 map 字面值语法来更详细地说明这一点:

func TestSplit(t *testing.T) {  
    tests := **map[string]struct {  
        input string  
        sep   string  
        want  []string  
    }**{   
        "simple":       {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},   
        "wrong sep":    {input: "a/b/c", sep: ",", want: []string{"a/b/c"}},  
        "no sep":       {input: "abc", sep: "/", want: []string{"abc"}},  
        "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},  
    }  
    for name, tc := range tests {  
        got := Split(tc.input, tc.sep)  
        if !reflect.DeepEqual(tc.want, got) {  
            t.Fatalf("**%s:** expected: %v, got: %v", **name**, tc.want, got)  
        }  
    }  
}

使用 map 字面值语法,我们不再将测试用例定义为结构的 slice,而是作为测试名到测试结构的 map。 使用可能会提高测试效果的 map 还有一个好处。

map 迭代顺序是 undefined1 这意味着每次运行 go test,我们的测试都可能以不同的顺序运行。

这对于发现在按语句顺序运行时测试通过的条件非常有用,但不适用于其他情况。如果您发现这种情况发生了,您可能是有一些全局状态,被一次测试改变,而后续测试取决于该修改。

Introducing sub tests

在我们修复失败的测试之前,还有一些其他问题需要在我们的 table driven test 工具中解决。

第一,我们在其中一个测试用例失败时调用t.Fatalf。 这意味着在第一次失败的测试用例之后我们停止测试其他情况。 因为测试用例是以未定义的顺序运行的,所以如果测试失败,那么知道它是唯一的失败还是只是第一次失败会更好。

如果我们努力将每个测试用例写出来作为测试包的函数,测试包将为我们做到这一点,但是这很冗长。 好消息是,自从Go 1.7添加了一项新功能,让我们可以轻松地进行 table driven test。 它们被称为 sub tests

func TestSplit(t *testing.T) {  
    tests := map[string]struct {  
        input string  
        sep   string  
        want  []string  
    }{  
        "simple":       {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},  
        "wrong sep":    {input: "a/b/c", sep: ",", want: []string{"a/b/c"}},  
        "no sep":       {input: "abc", sep: "/", want: []string{"abc"}},  
        "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}},  
    }  
    for name, tc := range tests {  
**t.Run(name, func(t *testing.T) {  
            got := Split(tc.input, tc.sep)  
            if !reflect.DeepEqual(tc.want, got) {  
                t.Fatalf("expected: %v, got: %v", tc.want, got)  
            }  
        })**  
    }  
}

由于每个 sub test 现在都有一个名称,我们可以在任何测试运行中自动打印出该名称。

% go test 
--- FAIL: TestSplit (0.00s)  
    --- FAIL: **TestSplit/trailing_sep** (0.00s)  
        split_test.go:25: expected: [a b c], got: [a b c ]

每个 subtest 都是它自己的匿名函数,因此我们可以使用 t.Fatalft.Skipf 和所有其他 testing.T helper,同时保留table driven test 的紧凑性。


目录
相关文章
This job is stuck, because the project doesn‘t have any runners online assigned to it. Go to Runners
This job is stuck, because the project doesn‘t have any runners online assigned to it. Go to Runners
This job is stuck, because the project doesn‘t have any runners online assigned to it. Go to Runners
译 | Prefer table driven tests(三)
译 | Prefer table driven tests(三)
106 0
A BRIEF INTRODUCTION TO STATISTICS | DEFINITION, SCOPE AND LIMITATIONS
Introduction to Statistics: In the modern world of computers and information technology, the importance of statistics is very well recognized by all the disciplines.
1452 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等