go test - goconvey

ddatsh

dev #go go-test

GoConvey 简介

GoConvey 网站 : https://smartystreets.github.io/goconvey

兼容 Go 内置 testing库,丰富的断言函数,彩色的控制台输出,提供命令行工具,更加直观的 Web 界面,测试会自动运行

go install github.com/smartystreets/goconvey@latest

运行 goconvey ,可以在本地运行一个强大 Web 图形界面,配合内置的 testing 使用,以提高开发效率

基本使用

产品代码

实现判断两个字符串切片是否相等的函数 StringSliceEqual,主要逻辑包括:

func StringSliceEqual(a, b []string) bool {
    if len(a) != len(b) {
        return false
    }

    if (a == nil) != (b == nil) {
        return false
    }

    for i, v := range a {
        if v != b[i] {
            return false
        }
    }
    return true
}
if (a == nil) != (b == nil) {
    return false
}

实例化a和b,[]string{} 和 []string(nil),这时两个字符串切片的长度都是 0,但不相等

测试代码

正常情况的测试用例:

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual should return true when a != nil  && b != nil", t, func() {
        a := []string{"hello", "goconvey"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeTrue)
    })
}
go test -v
=== RUN   TestStringSliceEqual

  TestStringSliceEqual should return true when a != nil  && b != nil ✔


1 total assertion

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok  	example	0.002s

So(StringSliceEqual(a, b), ShouldBeFalse) 故意不过后

go test -v -trimpath
=== RUN   TestStringSliceEqual

  TestStringSliceEqual should return true when a != nil  && b != nil ✘


Failures:

  * example/slice_test.go 
  Line 13:
  Expected: false
  Actual:   true


1 total assertion

--- FAIL: TestStringSliceEqual (0.00s)
FAIL
exit status 1
FAIL	example	0.003s

-trimpath 可去本地目录信息

再补充 3 个测试用例:

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual should return true when a != nil  && b != nil", t, func() {
        a := []string{"hello", "goconvey"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeTrue)
    })

    Convey("TestStringSliceEqual should return true when a == nil  && b == nil", t, func() {
        So(StringSliceEqual(nil, nil), ShouldBeTrue)
    })

    Convey("TestStringSliceEqual should return false when a == nil  && b != nil", t, func() {
        a := []string(nil)
        b := []string{}
        So(StringSliceEqual(a, b), ShouldBeFalse)
    })

    Convey("TestStringSliceEqual should return false when a != nil  && b != nil", t, func() {
        a := []string{"hello", "world"}
        b := []string{"hello", "goconvey"}
        So(StringSliceEqual(a, b), ShouldBeFalse)
    })
}

每一个 Convey 语句对应一个测试用例,一个函数的多个测试用例可以通过一个测试函数的多个 Convey 语句来呈现

=== RUN   TestStringSliceEqual

  TestStringSliceEqual should return true when a != nil  && b != nil ✔


1 total assertion


  TestStringSliceEqual should return true when a == nil  && b == nil ✔


2 total assertions


  TestStringSliceEqual should return false when a == nil  && b != nil ✔


3 total assertions


  TestStringSliceEqual should return false when a != nil  && b != nil ✔


4 total assertions

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok  	example	0.003s

Convey 语句的嵌套

Convey 语句可以无限嵌套,以体现测试用例之间的关系。只有最外层的 Convey 需要传入 *testing.T 类型的变量 t

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual", t, func() {
        Convey("should return true when a != nil  && b != nil", func() {
            a := []string{"hello", "goconvey"}
            b := []string{"hello", "goconvey"}
            So(StringSliceEqual(a, b), ShouldBeTrue)
        })

        Convey("should return true when a == nil  && b == nil", func() {
            So(StringSliceEqual(nil, nil), ShouldBeTrue)
        })

        Convey("should return false when a == nil  && b != nil", func() {
            a := []string(nil)
            b := []string{}
            So(StringSliceEqual(a, b), ShouldBeFalse)
        })

        Convey("should return false when a != nil  && b != nil", func() {
            a := []string{"hello", "world"}
            b := []string{"hello", "goconvey"}
            So(StringSliceEqual(a, b), ShouldBeFalse)
        })
    })
}
=== RUN   TestStringSliceEqual

  TestStringSliceEqual 
    should return true when a != nil  && b != nil ✔
    should return true when a == nil  && b == nil ✔
    should return false when a == nil  && b != nil ✔
    should return false when a != nil  && b != nil ✔


4 total assertions

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok  	example	0.003s

BDD 风格

一种三层嵌套的惯用法,核心点是通过 GWT(Given…When…Then)格式来描述测试用例,示例:

func TestStringSliceEqualIfBothNotNil(t *testing.T) {
    Convey("Given two string slice which are both not nil", t, func() {
       a := []string{"hello", "goconvey"}
       b := []string{"hello", "goconvey"}
       Convey("When the comparision is done", func() {
          result := StringSliceEqual(a, b)
          Convey("Then the result should be true", func() {
             So(result, ShouldBeTrue)
          })
       })
    })
}
=== RUN   TestStringSliceEqualIfBothNotNil

  Given two string slice which are both not nil 
    When the comparision is done 
      Then the result should be true ✔


1 total assertion

--- PASS: TestStringSliceEqualIfBothNotNil (0.00s)
PASS
ok  	example	0.003s

按 GWT 格式写测试用例时,每一组 GWT 对应一条测试用例,即最内层的 Convey 语句不像两层嵌套时可以有多个,而是只能有一个 Convey 语句

使用测试套的形式,一个测试函数包含多条用例,每条用例使用 Convey 语句四层嵌套的惯用法:

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqualIfBothNotNil", t, func() {
        Convey("Given two string slice which are both not nil", func() {
            a := []string{"hello", "goconvey"}
            b := []string{"hello", "goconvey"}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be true", func() {
                    So(result, ShouldBeTrue)
                })
            })
        })
    })

    Convey("TestStringSliceEqualIfBothNil", t, func() {
        Convey("Given two string slice which are both nil", func() {
            var a []string = nil
            var b []string = nil
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be true", func() {
                    So(result, ShouldBeTrue)
                })
            })
        })
    })

    Convey("TestStringSliceNotEqualIfNotBothNil", t, func() {
        Convey("Given two string slice which are both nil", func() {
            a := []string(nil)
            b := []string{}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be false", func() {
                    So(result, ShouldBeFalse)
                })
            })
        })
    })

    Convey("TestStringSliceNotEqualIfBothNotNil", t, func() {
        Convey("Given two string slice which are both not nil", func() {
            a := []string{"hello", "world"}
            b := []string{"hello", "goconvey"}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be false", func() {
                    So(result, ShouldBeFalse)
                })
            })
        })
    })

}

断言方法

一般相等类

So(thing1, ShouldEqual, thing2)
So(thing1, ShouldNotEqual, thing2)
So(thing1, ShouldResemble, thing2)		// 用于数组切片map和结构体相等
So(thing1, ShouldNotResemble, thing2)
So(thing1, ShouldPointTo, thing2)
So(thing1, ShouldNotPointTo, thing2)
So(thing1, ShouldBeNil)
So(thing1, ShouldNotBeNil)
So(thing1, ShouldBeTrue)
So(thing1, ShouldBeFalse)
So(thing1, ShouldBeZeroValue)

数字数量比较类

So(1, ShouldBeGreaterThan, 0)
So(1, ShouldBeGreaterThanOrEqualTo, 0)
So(1, ShouldBeLessThan, 2)
So(1, ShouldBeLessThanOrEqualTo, 2)
So(1.1, ShouldBeBetween, .8, 1.2)
So(1.1, ShouldNotBeBetween, 2, 3)
So(1.1, ShouldBeBetweenOrEqual, .9, 1.1)
So(1.1, ShouldNotBeBetweenOrEqual, 1000, 2000)
So(1.0, ShouldAlmostEqual, 0.99999999, .0001)   // tolerance is optional; default 0.0000000001
So(1.0, ShouldNotAlmostEqual, 0.9, .0001)

包含类

So([]int{2, 4, 6}, ShouldContain, 4)
So([]int{2, 4, 6}, ShouldNotContain, 5)
So(4, ShouldBeIn, ...[]int{2, 4, 6})
So(4, ShouldNotBeIn, ...[]int{1, 3, 5})
So([]int{}, ShouldBeEmpty)
So([]int{1}, ShouldNotBeEmpty)
So(map[string]string{"a": "b"}, ShouldContainKey, "a")
So(map[string]string{"a": "b"}, ShouldNotContainKey, "b")
So(map[string]string{"a": "b"}, ShouldNotBeEmpty)
So(map[string]string{}, ShouldBeEmpty)
So(map[string]string{"a": "b"}, ShouldHaveLength, 1) // supports map, slice, chan, and string

字符串类

So("asdf", ShouldStartWith, "as")
So("asdf", ShouldNotStartWith, "df")
So("asdf", ShouldEndWith, "df")
So("asdf", ShouldNotEndWith, "df")
So("asdf", ShouldContainSubstring, "稍等一下")		// optional 'expected occurences' arguments?
So("asdf", ShouldNotContainSubstring, "er")
So("adsf", ShouldBeBlank)
So("asdf", ShouldNotBeBlank)

panic 类

So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "")		// or errors.New("something")
So(func(), ShouldNotPanicWith, "")	// or errors.New("something")

类型检查类

So(1, ShouldHaveSameTypeAs, 0)
So(1, ShouldNotHaveSameTypeAs, "asdf")

时间和时间间隔类

So(time.Now(), ShouldHappenBefore, time.Now())
So(time.Now(), ShouldHappenOnOrBefore, time.Now())
So(time.Now(), ShouldHappenAfter, time.Now())
So(time.Now(), ShouldHappenOnOrAfter, time.Now())
So(time.Now(), ShouldHappenBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldNotHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenWithin, duration, time.Now())
So(time.Now(), ShouldNotHappenWithin, duration, time.Now())

要点归纳

  1. import goconvey 包时,前面加点号 “.",以减少冗余的代码
  2. 每个测试用例使用 Convey 语句包裹起来,推荐使用 Convey 语句的嵌套,即一个函数有一个或多个测试函数,一个测试函数嵌套两层、三层或四层 Convey 语句
  3. 使用 GoConvey 框架的 Web 界面特性,作为命令行的补充
  4. 在适当的场景下使用 SkipConvey 函数或 SkipSo 函数
  5. 当测试中有需要时,可以定制断言函数