译 | Prefer table driven tests(三)

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

Individual sub test cases can be executed directly

由于 sub tests 具有名称,因此您可以使用 go test -run flag 按名称运行一系列 sub tests。

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

Comparing what we got with what we wanted

现在我们已准备好修复测试用例。 我们来看看错误。

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

你能发现问题吗? 很明显,切片是不同的,这就是 reflect.DeepEqual 令人烦恼的原因。 但是要找到实际的差异并不容易,你必须发现在 c 之后额外的空格。 在这个简单的例子中,这可能看起来很简单,但是当你比较两个复杂的深层嵌套的gRPC结构时,它是任何东西(不一定是空格)。

如果我们切换到 %#v 语法以将值视为Go(ish)声明,则可以改进输出:

got := Split(tc.input, tc.sep)  
if !reflect.DeepEqual(tc.want, got) {  
    t.Fatalf("**expected: %#v, got: %#v**", tc.want, got)  
}

现在,当我们运行测试时,很明显问题在于 slice 中有一个额外的空白元素。

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

但是在我们开始修复测试失败之前,我想多谈一点关于选择正确的方法来呈现测试失败的问题。我们的 Split 函数很简单,它接受一个原始的字符串并返回一段字符串,但是如果它处理结构,或者更糟的,指向结构的指针呢?

下面是一个例子,其中 %v 不起作用:

func main() {  
    type T struct {  
        I int  
    }  
    x := []*T{ {1}, {2}, {3} }
    y := []*T{ {1}, {2}, {4} }
    fmt.Printf("%v %v\n", x, y)  
    fmt.Printf("%#v %#v\n", x, y)  
}

第一个 fmt.Printf 打印毫无帮助但符合预期的 addresses slice;[0xc000096000 0xc000096008 0xc000096010] [0xc000096018 0xc000096020 0xc000096028]。 但是,我们的 %#v 并没有做任何改进。打印一个 addresses slice 强制转换为 *main.T[]*main.T{(*main.T)(0xc000096000), (*main.T)(0xc000096008), (*main.T)(0xc000096010)} []*main.T{(*main.T)(0xc000096018), (*main.T)(0xc000096020), (*main.T)(0xc000096028)}

由于使用任何 fmt.Printf verb 的局限性,我想从Google引入 go-cmp 库。

cmp 库的目标是专门比较两个值。这类似于 reflect.DeepEqual,但它有更多的功能。所以,使用cmp pacakge,你可以编写如下:

func main() {  
    type T struct {  
        I int  
    }  
    x := []*T{ {1}, {2}, {3} }
    y := []*T{ {1}, {2}, {4} }
    fmt.Println(cmp.Equal(x, y)) **// false**  
}

但是对于我们的测试函数来说,更有用的是 cmp.Diff 函数,它将产生一个文本描述,递归地描述两个值之间的区别。

func main() {  
    type T struct {  
        I int  
    }  
    x := []*T{ {1}, {2}, {3} }
    y := []*T{ {1}, {2}, {4} }
    diff := cmp.Diff(x, y)  
    fmt.Printf(diff)  
}

取而代之输出:

% go run  
{[]*main.T}[2].I:  
         -: 3  
         +: 4

以上表明,在类型 T 的 slice 第二个元素,I 字段应该是3,但实际上是4。

综上所述,我们进行了 table driven go-cmp test

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)  
**diff := cmp.Diff(tc.want, got)  
            if diff != "" {  
                t.Fatalf(diff)  
            }**  
        })  
    }  
}

运行,我们得到

% go test  
--- FAIL: TestSplit (0.00s)  
    --- FAIL: TestSplit/trailing_sep (0.00s)  
        split_test.go:27: {[]string}[?->3]:  
                -: <non-existent>  
                +: ""  
FAIL  
exit status 1  
FAIL    split   0.006s

使用 cmp.Diff 我们的测试工具不仅仅是告诉我们,我们得到的和想要的是不同的。我们的测试告诉我们,字符串的长度是不同的,结构中的第三个索引不应该存在,但是实际输出得到一个空字符串,“”。从此开始,修复测试失败是直接了当的。

原文:https://dave.cheney.net/2019/05/07/prefer-table-driven-tests

本文作者 : cyningsun

本文地址https://www.cyningsun.com/07-24-2019/prefer-table-driven-tests-cn.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Golang

  1. 译|There Are No Reference Types in Go
  2. Go 语言没有引用类型,指针也与众不同
  3. 译|What “accept interfaces, return structs” means in Go
  4. 如何用好 Go interface
  5. 一个优雅的 LRU 缓存实现
目录
相关文章
|
22天前
|
Java Maven
The goal you specified requires a project to execute but there is no POM in this directory
The goal you specified requires a project to execute but there is no POM in this directory
36 0
|
11月前
|
存储 测试技术 Go
译 | Prefer table driven tests(二)
译 | Prefer table driven tests(二)
63 0
|
11月前
|
测试技术 Shell Go
译 | Prefer table driven tests(一)
译 | Prefer table driven tests
54 0
|
弹性计算 Linux Docker
从零学习SpringCloud系列(二):Schema specific part is opaque
从零学习SpringCloud系列(二):Schema specific part is opaque
187 0
|
Java 测试技术
出现Error creating bean with name与CONDITIONS EVALUATION REPORT问题
出现Error creating bean with name与CONDITIONS EVALUATION REPORT问题
306 0
出现Error creating bean with name与CONDITIONS EVALUATION REPORT问题
annotationProcessor‘ dependencies won‘t be recognized as kapt annotation processors. Please change
annotationProcessor‘ dependencies won‘t be recognized as kapt annotation processors. Please change
304 0
UDO report generate DDIC table
UDO report generate DDIC table
122 0
UDO report generate DDIC table
Action framework BAdI Definition TRIGGER_EXECUTED
Created by Jerry Wang, last modified on May 28, 2014
148 0
Action framework BAdI Definition TRIGGER_EXECUTED
How does ABAP check table work
Created by Wang, Jerry, last modified on Jan 06, 2017
101 0
How does ABAP check table work