搜索
简帛阁>技术文章>Golang学习笔记(三)测试框架

Golang学习笔记(三)测试框架

测试框架整理

Golang主要有以下四个测试框架:

  • GoConvey
  • GoStub
  • GoMock
  • Monkey

1、GoConvey简介

测试案例
用于测试的函数,判断两个字符串切片是否相同:

func StringSliceEqual(a, b []string) bool {<!-- -->
    if len(a) != len(b) {<!-- -->
        return false
    }
    // []string{}和[]string(nil),这时两个字符串切片的长度都是0,但不相等
    if (a == nil) != (b == nil) {<!-- -->
        return false
    }
    
    for i, v := range a {<!-- -->
        if v != b[i] {<!-- -->
            return false
        }
    }
    return true
}

测试代码 :

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

执行结果:

=== RUN   TestStringSliceEqual

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


1 total assertion

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s


一个函数多用例测试:
1、Convey非嵌套

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

执行结果:

=== 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

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s

2、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)
        })
    })
}

执行结果:

=== RUN   TestStringSliceEqual

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

2 total assertions

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s

Web界面
GoConvey不仅支持在命令行进行自动化编译测试,而且还支持在 Web 界面进行自动化编译测试。

$GOPATH/bin/goconvey

Web界面的主要功能:

  1. 可以设置界面主题
  2. 查看完整的测试结果
  3. 使用浏览器提醒等实用功能
  4. 自动检测代码变动并编译测试
  5. 半自动化书写测试用例
  6. 查看测试覆盖率
  7. 临时屏蔽某个包的编译测试

Skip
针对想忽略但又不想删掉或注释掉某些断言操作,GoConvey提供了Convey/So的Skip方法:

  • SkipConvey函数表明相应的闭包函数将不被执行
  • SkipSo函数表明相应的断言将不被执行

当存在SkipConvey或SkipSo时,测试日志中会显式打上"skipped"形式的标记:

  • 当测试代码中存在SkipConvey时,相应闭包函数中不管是否为SkipSo,都将被忽略,测试日志中对应的符号仅为一个"⚠"
  • 当测试代码Convey语句中存在SkipSo时,测试日志中每个So对应一个"✔"或"✘",每个SkipSo对应一个"⚠",按实际顺序排列
  • 不管存在SkipConvey还是SkipSo时,测试日志中都有字符串"{n} total assertions (one or more
    sections skipped)",其中{n}表示测试中实际已运行的断言语句数
摘自:https://www.jianshu.com/p/e3b2b1194830

2、GoStub简介

使用场景

  1. 基本场景:为一个全局变量打桩
  2. 基本场景:为一个函数打桩
  3. 基本场景:为一个过程打桩
  4. 复合场景:由任意相同或不同的基本场景组合而成

1. 为一个全局变量打桩
假设num为被测函数中使用的一个全局整型变量,当前测试用例中假定num的值为150,则打桩的代码如下:

stubs := Stub(&num, 150)
defer stubs.Reset()

stubs是GoStub框架的函数接口Stub返回的对象,该对象有Reset操作,即将全局变量的值恢复为原值。

2. 为一个函数打桩
假设这是我们产品的既有代码中定义的函数:

func Exec(cmd string, args ...string) (string, error) {<!-- -->
    ...
}

则Exec函数是不能通过GoStub框架打桩的。

若要想将Exec函数通过GoStub框架打桩,需要对该函数的声明做重构,即将Exec函数定义为匿名函数,同时将它赋值给Exec变量, 重构后的代码如下:

var Exec = func(cmd string, args ...string) (string, error) {<!-- -->
    ...
}

当Exec函数重构成Exec变量后,丝毫不影响既有代码中对Exec函数的调用。由于Exec变量是函数变量,所以我们一般将这类变
量也叫做函数。

现在我们可以对Exec函数打桩了,代码如下所示:

stubs := Stub(&Exec, func(cmd string, args ...string) (string, error) {<!-- -->
            return "xxx-vethName100-yyy", nil
})
defer stubs.Reset()

3. 为一个过程打桩
当一个函数没有返回值时,该函数我们一般称为过程。很多时候,我们将资源清理类函数定义为过程。

我们对过程DestroyResource的打桩代码为:

stubs := StubFunc(&DestroyResource)
defer stubs.Reset()

测试案例

结合GoConvey,测试函数中嵌套两级Convey语句,第一级Convey语句对应测试函数,第二级Convey语句对应测试用例。在第二级的每个Convey函数中都会产生一个stubs对象,彼此独立,互不影响。

func TestFuncDemo(t *testing.T) {<!-- -->
    Convey("TestFuncDemo", t, func() {<!-- -->
        Convey("for succ", func() {<!-- -->
            stubs := Stub(&num, 150)
            defer stubs.Reset()
            stubs.StubFunc(&Exec,"xxx-vethName100-yyy", nil)
            var liLei = `{"name":"LiLei", "age":"21"}`
            stubs.StubFunc(&adapter.Marshal, []byte(liLei), nil)
            stubs.StubFunc(&DestroyResource)
            //several So assert
        })

        Convey("for fail when num is too small", func() {<!-- -->
            stubs := Stub(&num, 50)
            defer stubs.Reset()
            //several So assert
        })

        Convey("for fail when Exec error", func() {<!-- -->
            stubs := Stub(&num, 150)
            defer stubs.Reset()
            stubs.StubFunc(&Exec, "", ErrAny)
            //several So assert
        })

        Convey("for fail when Marshal error", func() {<!-- -->
            stubs := Stub(&num, 150)
            defer stubs.Reset()
            stubs.StubFunc(&Exec,"xxx-vethName100-yyy", nil)
            stubs.StubFunc(&adapter.Marshal, nil, ErrAny)
            //several So assert
        })

    })
}

Gostub不适用的复杂情况

  1. 被测函数中多次调用了数据库读操作函数接口 ReadDb,并且数据库为key-value型。被测函数先是 ReadDb 了一个父目录的值,然后在 for 循环中读了若干个子目录的值。在多个测试用例中都有将ReadDb打桩为在多次调用中呈现不同行为的需求,即父目录的值不同于子目录的值,并且子目录的值也互不相等

  2. 被测函数中有一个循环,用于一个批量操作,当某一次操作失败,则返回失败,并进行错误处理。假设该操作为Apply,则在异常的测试用例中有将Apply打桩为在多次调用中呈现不同行为的需求,即Apply的前几次调用返回成功但最后一次调用却返回失败

  3. 被测函数中多次调用了同一底层操作函数,比如exec.Command,函数参数既有命令也有命令参数。被测函数先是创建了一个对象,然后查询对象的状态,在对象状态达不到期望时还要删除对象,其中查询对象是一个重要的操作,一般会进行多次重试。在多个测试用例中都有将 exec.Command 打桩为多次调用中呈现不同行为的需求,即创建对象、查询对象状态和删除对象对返回值的期望都不一样

摘自:https://www.jianshu.com/p/70a93a9ed186
测试框架整理Golang主要有以下四个测试框架:GoConveyGoStubGoMockMonkey1、GoConvey简介测试案例用于测试的函数,判断两个字符串切片是否相同:funcStringSl
Ginkgo测试框架学习笔记链接导航:Ginkgo主页Github源码文章目录:Ginkgo测试框架学习笔记一、安装Ginkgo二、框架拆解1、BootstrappingaSuite2、AddingS
golang日志框架logrus学习笔记golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数,对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持。
一、基础1HelloWorld程序demo:packagemainimportfmt//注释//注释funcmain(){fmtPrintf(HelloWorld\n)}执行:gorundemogo编
structstruct,一组字段的集合,类似其他语言的class放弃了大量包括继承在内的面向对象特性,只保留了组合(composition)这个最基础的特性1声明及初始化代码如下:typeperso
golan声明的变量必须要用到?语法a,b:2323;b为bool类型结构体的赋值需要用到逗号分隔字段并且最后一个字段后也必须加上逗号这和JavaScript的对象不一样哦golang要严谨写type
结构体定义typeTstruct{aintbint}ortypeTstruct{a,bint}结构体类型变量varsTsa5sb8结构体指针vart*Ttnew(T)ta1tb2ort:new(T)t
链表(Linkedlist是一种常见数据结构,但并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针。由于不必须按顺序存储,链表在插入的时候可以达到O(1),比顺序表快得多,但是查找
典(Map):map[K]TK:为键类型,T:为元素(值)类型。例:map[int]string一个键类型为int,值类型为string的字典类型Go语言的字典类型(map实际上是一个哈希表(ha
GoConvey是一款针对Golang测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多Web界面特性。GoConvey网站:http://smartystreetsgithu