如何使用标准GO测试软件包实现BDD实践



我想先编写测试,然后编写使测试通过的代码。

我可以编写这样的测试函数:

func TestCheckPassword(t *testing.T) {
    isCorrect := CheckPasswordHash("test", "$2a$14$rz.gZgh9CHhXQEfLfuSeRuRrR5uraTqLChRW7/Il62KNOQI9vjO2S")
    if isCorrect != true {
        t.Errorf("Password is wrong")
    }
}

,但我想为每个测试功能提供更多描述性信息。

例如,我正在考虑为我的应用创建Auth模块。现在,用普通的英语,我可以轻松地描述我对此模块的要求:

  1. 它应该接受非空字符串作为输入。
  2. 字符串必须从6到48个字符长。
  3. 函数应返回true如果拟合提供的哈希字符串和false(如果不是(。

除了将其评论以外,将这些信息放在测试中可以理解的方法是什么?

在GO中,编写测试进行相关检查的常见方法是创建一小部分测试用例(该案例称为" Table" 和该方法为"表驱动的测试" (,我们只是循环并执行一对一。

测试案例可能具有任意属性,通常由匿名结构建模。
如果要为测试用例提供描述,可以向描述测试用例的结构添加一个其他字段。这既可以用作测试案例的文档,也将作为((一部分( output 的一部分,以防测试案例失败。

为简单起见,让我们测试以下简单的Abs()函数:

func Abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

实施似乎是正确而完整的。如果我们想为此编写测试,通常我们会添加2个测试用例以覆盖两个可能的分支:测试x何时为负(x < 0(,而x何时非负值。实际上,它通常很方便,也建议还测试特殊的0输入和角案例:输入的最小值和最大值。

如果考虑到它,则此Abs()函数在以int32的最小值为单位时甚至不会给出正确的结果,因为那是-2147483648,并且其绝对值为2147483648,它不适合int32,因为最大值值int32的属于:2147483647。因此,上述实现将溢出,并且错误地给出了负的最小值作为负最小值的绝对值。

列出每个可能分支的情况的测试功能,以及包括0和角案例,并具有描述:

func TestAbs(t *testing.T) {
    cases := []struct {
        desc string // Description of the test case
        x    int32  // Input value
        exp  int32  // Expected output value
    }{
        {
            desc: "Abs of positive numbers is the same",
            x:    1,
            exp:  1,
        },
        {
            desc: "Abs of 0 is 0",
            x:    0,
            exp:  0,
        },
        {
            desc: "Abs of negative numbers is -x",
            x:    -1,
            exp:  1,
        },
        {
            desc: "Corner case testing MaxInt32",
            x:    math.MaxInt32,
            exp:  math.MaxInt32,
        },
        {
            desc: "Corner case testing MinInt32, which overflows",
            x:    math.MinInt32,
            exp:  math.MinInt32,
        },
    }
    for _, c := range cases {
        got := Abs(c.x)
        if got != c.exp {
            t.Errorf("Expected: %d, got: %d, test case: %s", c.exp, got, c.desc)
        }
    }
}

在GO中,编写这类测试的惯用方法是:

func TestCheckPassword(t *testing.T) {
    tcs := []struct {
        pw string
        hash string
        want bool
    }{
        {"test", "$2a$14$rz.gZgh9CHhXQEfLfuSeRuRrR5uraTqLChRW7/Il62KNOQI9vjO2S", true},
        {"foo", "$2a$14$rz.gZgh9CHhXQEfLfuSeRuRrR5uraTqLChRW7/Il62KNOQI9vjO2S", false},
        {"", "$2a$14$rz.gZgh9CHhXQEfLfuSeRuRrR5uraTqLChRW7/Il62KNOQI9vjO2S", false},
    }
    for _, tc := range tests {
        got := CheckPasswordHash(tc.pw, tc.hash)
        if got != tc.want {
            t.Errorf("CheckPasswordHash(%q, %q) = %v, want %v", tc.pw, tc.hash, got, want)
        }
    }
}

这被称为"表驱动测试"。您可以创建一个输入和预期输出表,在该表上迭代并调用您的功能,如果预期输出与您想要的内容不匹配,则编写一个描述失败的错误消息。

如果您想要的不像将回报与黄金价值进行比较的简单 - 例如,您要检查错误或值的返回,或者返回了良好的Hash SALT,但不在乎使用什么盐(因为那不是API的一部分(,您会为此编写其他代码 - 最后,您只需写下结果应该具有的属性,如果结果不如预期,请添加一些IF检查并提供描述性错误消息。所以,说:

func Hash(pw string) (hash string, err error) {
    // Validate input, create salt, hash thing…
}
func TestHash(t *testing.T) {
    tcs := []struct{
        pw string
        wantError bool
    }{
        {"", true},
        {"foo", true},
        {"foobar", false},
        {"foobarbaz", true},
    }
    for _, tc := range tcs {
        got, err := Hash(tc.pw)
        if err != nil {
            if !tc.wantError {
                t.Errorf("Hash(%q) = %q, %v, want _, nil", tc.pw, got, err)
            }
            continue
        }
        if len(got) != 52 {
            t.Errorf("Hash(%q) = %q, want 52 character string", tc.pw, got)
        }
        if !CheckPasswordHash(tc.pw, got) {
            t.Errorf("CheckPasswordHash(Hash(%q)) = false, want true", tc.pw)
        }
    }
}

如果您想要具有描述性文本和上下文的测试套件(例如Ruby的RSPEC(,则应查看Ginko:https://onsi.github.io/ginkgo/

最新更新