如何使用标准Go测试包实现BDD实践?

时间:2018-02-20 10:22:49

标签: testing go

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

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

>  Process: com.ensis.recyclerviewwithyoutubeplayer, PID: 12751
                                                                                     android.view.InflateException: Binary XML file line #7: Binary XML file line #7: Error inflating class fragment
                                                                                         at android.view.LayoutInflater.inflate(LayoutInflater.java:539)
                                                                                         at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
                                                                                         at com.ensis.recyclerviewwithyoutubeplayer.YoutubeVideoAdapter.onCreateViewHolder(YoutubeVideoAdapter.java:32)
                                                                                         at com.ensis.recyclerviewwithyoutubeplayer.YoutubeVideoAdapter.onCreateViewHolder(YoutubeVideoAdapter.java:15)
                                                                                         at android.support.v7.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:6519)
                                                                                         at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5706)
                                                                                         at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5589)
                                                                                         at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5585)
                                                                                         at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2231)
                                                                                         at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1558)
                                                                                         at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1518)
                                                                                         at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:610)
                                                                                         at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3719)
                                                                                         at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:3135)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.widget.RelativeLayout.measureChild(RelativeLayout.java:676)
                                                                                         at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:479)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5995)
                                                                                         at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
                                                                                         at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:139)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5995)
                                                                                         at android.support.v7.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:400)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5995)
                                                                                         at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5995)
                                                                                         at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
                                                                                         at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
                                                                                         at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5995)
                                                                                         at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
                                                                                         at com.android.internal.policy.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2662)
                                                                                         at android.view.View.measure(View.java:18911)
                                                                                         at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2157)
                                                                                         at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1261)
                                                                                         at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1498)
                                                                                         at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1140)
                                                                                         at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6239)
                                                                                         at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
                                                                                         at android.view.Choreographer.doCallbacks(Choreographer.java:670)
                                                                                         at android.view.Choreographer.doFrame(Choreographer.java:606)
                                                                                         at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
                                                                                         at android.os.Handler.handleCallback(Handler.java:739)
                                                                                         at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                         at android.os.Looper.loop(Looper.java:148)
                                                                                         at android.app.ActivityThread.main(ActivityThread.java:5551)
                                                                                         at java.lang.reflect.Method.invoke(Native Method)
                                                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:730)
                                                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:620)
                                                                                     Caused b

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

例如,我正在考虑为我的应用创建auth模块。 现在,用简单的英语,我可以很容易地描述我对这个模块的要求:

  1. 它应该接受非空字符串作为输入。
  2. 字符串长度必须为6到48个字符。
  3. 如果密码字符串适合提供的哈希字符串,则函数应返回true,否则返回false。
  4. 除非将这些信息放入评论之外,将非信息技术人员可以理解的信息放入测试中的方法是什么?

3 个答案:

答案 0 :(得分:1)

在Go中,编写测试以执行相关检查的常用方法是创建一个测试用例片段(称为" table" ,方法为"表驱动测试" ),我们只是循环并逐个执行。

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

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

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

实施似乎是正确和完整的。如果我们想为此编写测试,通常我们会添加2个测试用例以涵盖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)
        }
    }
}

答案 1 :(得分:0)

在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)
        }
    }
}

这称为“表驱动测试”。您创建一个输入和预期输出的表,您迭代该表并调用您的函数,如果预期的输出与您想要的不匹配,您将编写一条描述失败的错误消息。

如果您想要的并不像将回报与黄金值进行比较那么简单 - 例如,您要检查是否返回了错误或值,或者是否返回了格式正确的哈希+ 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)
        }
    }
}

答案 2 :(得分:0)

如果你想要一个带有描述性文本和上下文的测试套件(比如rubpec的rspec),你应该看看ginko:https://onsi.github.io/ginkgo/