译 | 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 的紧凑性。


目录
相关文章
|
2月前
|
Java Maven 数据库
Annotation Processing Tool自动生成代码
本文介绍了一种利用Java注解处理器(Annotation Processor)自动生成协议接收与发送类接口的方法,显著提升开发效率。注解处理器能在编译阶段扫描并处理特定注解,生成所需Java代码。文中详细展示了如何通过自定义`HttpProto`注解及对应的处理器`ProtoServiceProcessor`,实现在保存协议类后自动生成客户端请求工具和服务端控制器代码。此外,还提供了具体实现步骤、依赖配置及常见问题解决方案,如处理“服务配置文件不正确”错误和Gradle项目的配置方法。此技术特别适用于需要频繁处理协议或数据交互的应用场景。
28 1
|
测试技术 Shell Go
译 | Prefer table driven tests(一)
译 | Prefer table driven tests
80 0
|
缓存 测试技术 Go
译 | Prefer table driven tests(三)
译 | Prefer table driven tests(三)
83 0
|
弹性计算 Linux Docker
|
Java 测试技术
出现Error creating bean with name与CONDITIONS EVALUATION REPORT问题
出现Error creating bean with name与CONDITIONS EVALUATION REPORT问题
349 0
出现Error creating bean with name与CONDITIONS EVALUATION REPORT问题
从零学习SpringCloud系列(二):Schema specific part is opaque
从零学习SpringCloud系列(二):Schema specific part is opaque
226 0
annotationProcessor‘ dependencies won‘t be recognized as kapt annotation processors. Please change
annotationProcessor‘ dependencies won‘t be recognized as kapt annotation processors. Please change
354 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.
1425 0
|
计算机视觉
How do you create a DynamicResourceBinding that supports Converters, StringFormat?
原文 How do you create a DynamicResourceBinding that supports Converters, StringFormat? 2 down vote accepted In the past I've resorted to using se...
918 0