我想先编写测试,然后编写使测试通过的代码。
我可以编写这样的测试函数:
func TestCheckPassword(t *testing.T) {
isCorrect := CheckPasswordHash("test", "$2a$14$rz.gZgh9CHhXQEfLfuSeRuRrR5uraTqLChRW7/Il62KNOQI9vjO2S")
if isCorrect != true {
t.Errorf("Password is wrong")
}
}
,但我想为每个测试功能提供更多描述性信息。
例如,我正在考虑为我的应用创建Auth模块。现在,用普通的英语,我可以轻松地描述我对此模块的要求:
- 它应该接受非空字符串作为输入。
- 字符串必须从6到48个字符长。
- 函数应返回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/