Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Go语言单元测试和仿冒

8
Mar
2017

Go语言单元测试和仿冒

By Alex
/ in Go,Test
/ tags 单元测试
0 Comments
testing
单元测试

Go语言提供了一个轻量级的测试框架,此框架由testing包和 go test -run命令组成。

要编写测试用例,你需要创建一个以 _test.go结尾的源文件。该文件中包含一个或多个如下签名的函数:

Go
1
2
// Test后面的第一个字母不能是小写
func TestXxx(t *testing.T)

如果想让测试失败,调用testing.T的方法:

Go
1
2
3
4
5
6
7
8
if err != nil {
    // 导致测试失败
    t.Errorf("Test failed: %s", err.Error())
    return
 
    // 或者直接panic
    t.Fatalf("Test failed: %s", err.Error())
}
基准测试

testing包还支持性能基准测试。要执行基准测试,调用命令 go test -bench。

基准测试方法的签名如下:

Go
1
2
3
4
5
6
func BenchmarkXxx(*testing.B){
    // 目标逻辑必须运行b.N次,N根据实际情况调整,使测试结果尽量可靠
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

如果要基准测试并发执行的性能,可以使用 go test -cpu标记,并且调用助手函数: 

Go
1
2
3
4
5
6
7
8
9
10
11
12
func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    // 调用该助手函数触发并行测试
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        // 返回是否还有更多的迭代需要执行
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}
验证样例 

testing包还支持执行并验证样例代码。在被验证方法中,你可以通过注释声明期望的标准输出。 如果被验证方法的标准输出和注释匹配(不考虑首尾空白符),则验证通过。

示例:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func ExampleHello() {
    fmt.Println("hello")
    // 期望输出必须以Output:开头
    // Output: hello
}
 
func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // 可以放在多行注释中
    // Output:
    // hello, and
    // goodbye
}
 
func ExamplePerm() {
    for _, value := range Perm(4) {
        fmt.Println(value)
    }
    // 验证时不考虑输出行的顺序
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

没有期望输出注释的Example方法,虽然编译,单是不会被执行。

样例方法的命名约定:

Go
1
2
3
4
func Example() { ... }
func ExampleF() { ... }     // 函数F的样例代码
func ExampleT() { ... }     // 类型T的样例代码
func ExampleT_M() { ... }   // 类型T的M方法的样例代码
跳过测试

在运行时,你可以跳过一部分单元测试、性能基准测试。

Go
1
2
3
4
5
6
7
func TestTimeConsuming(t *testing.T) {
    // 在短测试模式下跳过此测试
    if testing.Short() {
        t.Skip("skipping test in short mode.")
    }
    ...
}
子测试

单元测试/基准测试支持“子测试”,你不需要定义额外的函数就可以实现表驱动(table-driven)的基准测试或层次化的单元测试。使用子测试还可以用来共享setup/teardown代码。

串行执行
Go
1
2
3
4
5
6
7
8
func TestFoo(t *testing.T) {
    // setup code here
    // t.Run运行t的子测试
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // teardown code here
}

每个子测试必须具有唯一性的名称。传递给go test的名字是:顶级测试的名称/子测试的名称,以及一个可选的后缀序列号(去歧义)。

你可以通过命令行执行需要执行哪些测试、哪些子测试:

Shell
1
2
3
4
go test -run ''      # 运行所有测试
go test -run Foo     # 运行所有名称以Foo开头的顶级测试
go test -run Foo/A=  # 运行所有名称以Foo开头的顶级测试的匹配"A="的子测试(也就是子测试名称为A,不限制序列号)
go test -run /A=1    # 运行所有名称以Foo开头的顶级测试的匹配"A=1"的子测试
并行执行

你可以用如下的方法并行执行子测试:

Go
1
2
3
4
5
6
7
8
9
10
11
12
// t是父测试
func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // 捕获变量供闭包使用
        // t.Run以子测试(of t)的形式运行tc
        t.Run(tc.Name, func(t *testing.T) {
            // 提示当前测试应当和其它并行(也就是同样调用t.Parallel的)测试并行的运行
            t.Parallel()
            ...
        })
    }
}

所有子测试都完毕之后,父测试才会完成。 

testing.M

如果有以下需求:

  1. 在测试之前之后执行setup/teardown逻辑
  2. 控制什么代码在主线程中执行

你可以考虑使用: func TestMain(m *testing.M) 

如果测试源文件中包含如上签名的方法,那么go test不会直接运行测试,而是调用TestMain方法。

TestMain会在主线程中执行,你可以在调用m.Run运行具体测试之前、之后提供任何setup/teardown代码:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func TestMain(m *testing.M) {
    // setup
    // 如果传入参数,必须手工调用:
    flag.Parse()
 
    // test
    exitCode := m.Run()
 
    // teardown
    ...
 
    // exit
    os.Exit(exitCode)
}
ginkgo

一个行为驱动测试框架,参考:Ginkgo学习笔记

gomock
简介

gomock是一个通用的仿冒框架,可以和testing包很好的集成。

mockgen

我们通过mockgen这个命令行工具来生成仿冒代码:

Shell
1
go install github.com/golang/mock/mockgen@v1.6.0
两种模式

mockgen提供两种不同的操作模式:

  1. source模式,该模式下,你可以从既有源文件来生成仿冒接口:
    Shell
    1
    mockgen -source=foo.go
  2. 反射模式,该模式下,会基于反射机制来自动识别需要仿冒的接口:
    Shell
    1
    2
    3
    #       导入路径,.表示当前目录对应导入路径
    #                           符号列表
    mockgen database/sql/driver Conn,Driver
命令行标记

-source 包含需要被仿冒的接口的源文件
-destination 生成的仿冒源码存放到的目标文件
-package  生成的仿冒源码使用的包名
-imports 生成的仿冒源码需要明确使用的imports列表,格式foo=bar/baz,foo=bar/baz其中foo是导入包在仿冒源文件中的标识符,bar/baz是被导入的包的路径
-aux_files 辅助文件列表,辅助文件可以是主源文件中的内嵌接口的定义所在的文件,格式foo=bar/baz.go,foo是辅助文件所在包在仿冒源文件中的标识符
-build_flags 反射模式下传递给go build的标记
-mock_names 为每个生成的Mock指定名字,例如Repository=MockSensorRepository,Endpoint=MockSensorEndpoint,键是被仿冒接口的名字
-self_package 生成的代码的完整导入路径,用于防止循环导入
-copyright_file 版权头文件片段

编写测试

被测试接口:

Go
1
2
3
4
5
6
7
type Foo interface {
  Bar(x int) int
}
 
func SUT(f Foo) {
// ...
}

测试用例: 

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func TestFoo(t *testing.T) {
  ctrl := gomock.NewController(t)
 
  // Go 1.14+, mockgen 1.5.0+ 不再需要显式调用
  defer ctrl.Finish()
 
  m := NewMockFoo(ctrl)
 
  // 断言第一次(也是唯一一次)Bar调用的入参是99,其它任何调用都失败
  m.
    EXPECT().
    Bar(gomock.Eq(99)).
    Return(101)
 
  m.
    EXPECT(). // 期望每一次
    Bar(gomock.Eq(99)). // 入参99时
    DoAndReturn(func(_ int) int { // 休眠1秒然后会返回101
      time.Sleep(1*time.Second)
      return 101
    }).
    AnyTimes()
 
 
  SUT(m)
}
gock
简介

这是一个HTTP流量仿冒和测试工具,特性包括:

  1. 简单易用的链式调用API
  2. 声明式的仿冒DSL
  3. 内置助手用于简化XML/JSON响应仿冒
  4. 完整的基于正则式的HTTP请求匹配
  5. 基于请求方法、URL参数、头、体进行请求匹配
  6. 可扩展、可拔插的请求匹配规则
  7. 支持在仿冒/真实网络模式之间切换
  8. 可以和任何net/http兼容的客户端协作
  9. 网络延迟模拟
  10. 无外部依赖

gock的工作原理:

  1. 通过http.DefaultTransport或自定义的http.Transport来拦截HTTP出站请求
  2. 以FIFO声明顺序,将出站请求和HTTP仿冒期望(mock expectations)池中的仿冒进行匹配
  3. 如果至少匹配一个仿冒,则此仿冒负责产生HTTP响应
  4. 如果没有匹配的仿冒,则默认报错,除非真实网络模式被开启 —— 导致执行真实的HTTP请求
安装
Shell
1
go get -u gopkg.in/h2non/gock.v1
样例
简单例子
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package test
 
import (
  "github.com/nbio/st"
  "gopkg.in/h2non/gock.v1"
  "io/ioutil"
  "net/http"
  "testing"
)
 
func TestSimple(t *testing.T) {
  // 清理
  defer gock.Off()
 
  // 仿冒对http://foo.com/bar的GET请求,返回200状态码和JSON响应
  gock.New("http://foo.com").
    Get("/bar").
    Reply(200).
    JSON(map[string]string{"foo": "bar"})
 
  // 下面的测试代码被拦截
  res, err := http.Get("http://foo.com/bar")
  st.Expect(t, err, nil)
  st.Expect(t, res.StatusCode, 200)
 
  body, _ := ioutil.ReadAll(res.Body)
  st.Expect(t, string(body)[:13], `{"foo":"bar"}`)
 
  // 期望没有未决的mock
  st.Expect(t, gock.IsDone(), true)
}
请求头匹配 
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package test
 
import (
  "github.com/nbio/st"
  "gopkg.in/h2non/gock.v1"
  "io/ioutil"
  "net/http"
  "testing"
)
 
func TestMatchHeaders(t *testing.T) {
  defer gock.Off()
 
  gock.New("http://foo.com").
    // 匹配请求头,使用正则式
    MatchHeader("Authorization", "^foo bar$").
    MatchHeader("API", "1.[0-9]+").
    // 要求请求头Accept存在
    HeaderPresent("Accept").
    Reply(200).
    // 以字符串形式指定响应体
    BodyString("foo foo")
 
 
  req, err := http.NewRequest("GET", "http://foo.com", nil)
  req.Header.Set("Authorization", "foo bar")
  req.Header.Set("API", "1.0")
  req.Header.Set("Accept", "text/plain")
  ...
}
请求体匹配 
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package test
 
import (
  "bytes"
  "github.com/nbio/st"
  "gopkg.in/h2non/gock.v1"
  "io/ioutil"
  "net/http"
  "testing"
)
 
func TestMockSimple(t *testing.T) {
  defer gock.Off()
 
  gock.New("http://foo.com").
    Post("/bar").
    // 以JSON方式匹配请求体
    MatchType("json").
    // 请求体必须包含foo字段,值为bar
    JSON(map[string]string{"foo": "bar"}).
    Reply(201).
    JSON(map[string]string{"bar": "foo"})
 
  body := bytes.NewBuffer([]byte(`{"foo":"bar"}`))
  res, err := http.Post("http://foo.com/bar", "application/json", body)
}
拦截客户端 
Go
1
2
3
req, err := http.NewRequest("GET", "http://foo.com", nil)
client := &http.Client{Transport: &http.Transport{}}
gock.InterceptClient(client)
启用真实网络 
Go
1
2
3
4
defer gock.DisableNetworking()
 
gock.EnableNetworking()
gock.New("http://httpbin.org")...
打印请求内容
Go
1
gock.Observe(gock.DumpRequest)
实践 
先声明仿冒再测试

编写实际测试逻辑之前,先把仿冒做好:

Go
1
2
3
4
5
6
7
8
9
10
func TestFoo(t *testing.T) {
  defer gock.Off() // 再测试完成之后,刷空未决仿冒
 
  gock.New("http://server.com").
    Get("/bar").
    Reply(200).
    JSON(map[string]string{"foo": "bar"})
 
  // 在这里编写测试代码
}
竞态条件

如果你的测试代码是并发的,无比预先准备好仿冒。gock不是线程安全的。

先具体再一般 

如果你需要编写一系列仿冒,那么,先编写具体化的、精确匹配请求的仿冒,然后再编写一般化的、通配的仿冒。

这样可以保证具体化的仿冒优先被测试是否匹配请求。

仅拦截客户端一次

你仅仅需要在测试开始之前,拦截客户端一次:

Go
1
gock.InterceptClient(client)
取消客户端拦截

在运行完测试场景之后,应当取消对客户端的拦截:

Go
1
2
3
4
function TestGock (t *testing.T) {
    defer gock.Off()
    defer gock.RestoreClient(client)
}

如果你使用的是http.DefaultClient或者http.DefaultTransport,不需要取消拦截。 

govcr

手工编写Mock的困难在于如何精确的模拟依赖的行为。如果依赖已经开发完毕,而你需要实现可重复的、基于仿冒的单元测试,可以考虑将依赖的行为“录制”下来,并依此实现Mock。

govcr就是一个能实现HTTP交互录制/回放的开源项目,它同时支持回放成功、失败的HTTP事务。它本质上是 http.Client的包装器。

安装
Shell
1
2
3
4
5
6
7
8
go get github.com/seborama/govcr
 
# 或者,明确指定兼容性版本,例如v4.x
go get gopkg.in/seborama/govcr.v4
 
 
# 导入路径
import "gopkg.in/seborama/govcr.v4"
术语
术语 说明
VCR

磁带录像机(Video Cassette Recorder),表示govcr提供的录制回放引擎,以及它产生的所有数据

VCR可以进行HTTP录制和回放,重复的请求回基于先前录制的信息  —— 位于磁盘中cassette文件中的track —— 直接返回,新请求则真正转发给真实服务器

cassette 一系列track的集合,默认保存在./govcr-fixtures目录下,形式为JSON文件,扩展名.cassette
Long Play cassette 以GZIP压缩的cassette,只需要以.gz后缀声明cassette名称即可启用
track

一个录制的HTTP请求,包括请求数据、响应数据,发生的错误

如果存在多个匹配请求的Track,则根据它们录制的顺序,依次进行回放

PCB

印刷电路板(Printed Circuit Board),能对VCR的某方面行为进行定制,例如:

  1. 禁用录制
  2. 匹配track时,忽略某些请求头
VCRConfig

此结构用于配置govcr记录器:

Go
1
2
3
4
5
6
7
8
9
10
vcr := govcr.NewVCR("MyCassette", &govcr.VCRConfig{
    // 录像存储路径
    CassettePath: "./govcr-fixtures",
    // 禁用录制(但是如果有匹配的track仍然会回放)
    DisableRecording: true,
    // 禁用日志
    Logging: false,
    // 不录制TLS数据
    RemoveTLS: true,
})
过滤器

某些情况下,请求无法匹配到已经录制的track。例如请求中包含一个时间戳参数,后者动态变化的标识符。另外一些情况下,响应需要进行转换。应对这些场景,你需要使用过滤器:

  1. RequestFilter处理当前真实请求、Track上的请求,例如删除某个请求头,从而影响它们的匹配
  2. ResponseFilter可以在返回给Client之前对响应进行预处理

这些转换操作不会持久化,也就是它不会影响录制的Track。

示例
HelloWorld
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
 
import (
    "fmt"
    "github.com/seborama/govcr"
)
 
const example1CassetteName = "MyCassette1"
 
func Example1() {
    vcr := govcr.NewVCR(example1CassetteName, nil)
    vcr.Client.Get("http://example.com/foo")
    fmt.Printf("%+v\n", vcr.Stats())
}
定制Transport

某些情况下,你的应用程序会创建自己的http.Client包装器,或者初始化自己的http.Transport(例如使用HTTPS的时候),你可以传递自己的http.Client对象给VCR,VCR会包装它,你需要使用包装后的http.Client:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main
 
import (
    "crypto/tls"
    "fmt"
    "net/http"
    "time"
 
    "github.com/seborama/govcr"
)
 
const example2CassetteName = "MyCassette2"
 
type myApp struct {
    httpClient *http.Client
}
 
func (app myApp) Get(url string) {
    app.httpClient.Get(url)
}
 
 
func Example2() {
    // 创建自定义的Transport
    tr := http.DefaultTransport.(*http.Transport)
    tr.TLSClientConfig = &tls.Config{
        InsecureSkipVerify: true, // 禁用TLS安全检查
    }
 
    myapp := &myApp{
        // 使用自定义传输    
        httpClient: &http.Client{
            Transport: tr,
            Timeout:   15 * time.Second,
        },
    }
 
    // 将Client传递给VCR
    vcr := govcr.NewVCR(example2CassetteName,
        &govcr.VCRConfig{
            Client: myapp.httpClient,
    })
 
    // 使用注入后的HttpClient
    myapp.httpClient = vcr.Client
 
    myapp.Get("https://example.com/foo")
    fmt.Printf("%+v\n", vcr.Stats())
}
使用过滤器

下面是请求过滤器的例子:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
 
import (
    "fmt"
    "strings"
    "time"
    "net/http"
    "github.com/seborama/govcr"
)
 
const example4CassetteName = "MyCassette4"
 
func Example4() {
    vcr := govcr.NewVCR(example4CassetteName,
        &govcr.VCRConfig{
            RequestFilters: govcr.RequestFilters{
                // 删除当前请求、Track请求的指定请求头后再进行匹配
                govcr.RequestDeleteHeaderKeys("X-Custom-My-Date"),
            },
            Logging: true,
        })
 
    req, err := http.NewRequest("POST", "http://example.com/foo", nil)
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Add("X-Custom-My-Date", time.Now().String())
    vcr.Client.Do(req)
    fmt.Printf("%+v\n", vcr.Stats())
}

下面的例子同时使用请求过滤器、响应过滤器:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
func Example5() {
    vcr := govcr.NewVCR(example5CassetteName,
        &govcr.VCRConfig{
            RequestFilters: govcr.RequestFilters{
                govcr.RequestDeleteHeaderKeys("X-Transaction-Id"),
            },
            ResponseFilters: govcr.ResponseFilters{
                 // 使用请求头中的X-Transaction-Id覆盖响应头中的X-Transaction-Id
                 govcr.ResponseTransferHeaderKeys("X-Transaction-Id"),
            },
            Logging: true,
        })
}

下面展示过滤器的高级用法:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
func Example6() {
    cfg := govcr.VCRConfig{
        Logging: true,
    }
 
    // 请求过滤器:将URL中的/order/{random} 重写为 /order/1234
    replacePath := govcr.RequestFilter(func(req govcr.Request) govcr.Request {
        // 重写
        req.URL.Path = "/order/1234"
        return req
    })
    // 条件性过滤器,仅当URL路径匹配example.com/order时才启用此过滤器
    replacePath = replacePath.OnPath(`example\.com\/order\/`)
 
    // 添加过滤器到VCRConfig
    cfg.RequestFilters.Add(replacePath)
    cfg.RequestFilters.Add(govcr.RequestDeleteHeaderKeys("X-Transaction-Id"))
 
    // 响应过滤器
    cfg.ResponseFilters.Add(
        // 覆盖响应头
        govcr.ResponseTransferHeaderKeys("X-Transaction-Id"),
 
        // 修改状态码
        func(resp govcr.Response) govcr.Response {
            if resp.StatusCode == http.StatusNotFound {
                resp.StatusCode = http.StatusAccepted
            }
            return resp
        },
 
        // 如果HTTP方法为GET,则添加响应头
        govcr.ResponseFilter(func(resp govcr.Response) govcr.Response {
            resp.Header.Add("method-was-get", "true")
            return resp
        }).OnMethod(http.MethodGet),
 
        // 如果HTTP方法为POST,则添加响应头
        govcr.ResponseFilter(func(resp govcr.Response) govcr.Response {
            resp.Header.Add("method-was-post", "true")
            return resp
        }).OnMethod(http.MethodPost),
 
        // 使用关联的请求的信息
        govcr.ResponseFilter(func(resp govcr.Response) govcr.Response {
            url := resp.Request().URL
            resp.Header.Add("get-url", url.String())
            return resp
        }).OnMethod(http.MethodGet),
    )
} 
testify

此包提供通用的断言、仿冒功能。

assert

该子包提供断言功能。示例:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package yours
 
import (
  "testing"
  "github.com/stretchr/testify/assert"
)
 
func TestAssertions(t *testing.T) {
 
  // 断言相等
  assert.Equal(t, 123, 123, "they should be equal")
  // 断言不等
  assert.NotEqual(t, 123, 456, "they should not be equal")
 
  // 断言为空
  assert.Nil(t, object)
  // 断言不为空
  if assert.NotNil(t, object) {
    // 进一步断言
    assert.Equal(t, "Something", object.Value)
  }
 
  // assertThat
  i := 0
  assert.Condition(t, func() bool { return i == 0 })
 
  // 断言函数调用有错误
  actualObj, err := SomeFunction()
  if assert.Error(t, err) {
    // ...
  }
 
  // 断言字符串、列表、映射包含指定的元素或子串
  assert.Contains(t, "Hello World", "World")
  assert.Contains(t, ["Hello", "World"], "World")
  assert.Contains(t, {"Hello": "World"}, "Hello")
 
  // 断言为空,也就是为nil、0、false,或者切片、通道长度为0
  assert.Empty(t, obj)
}
mock

该包提供仿冒功能:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package testify
 
import (
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "testing"
)
 
// 仿冒对象
type MyMockedObject struct {
    mock.Mock
}
 
// DoSomething是模拟的对象所具有的接口
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
 
    // 告知仿冒对象的方法被调用的事实
    // 获取入参所关联的Arguments
    args := m.Called(number)
    // 返回Arguments的第1、2个元素
    return args.Bool(0), args.Error(1)
 
}
 
func TestSomething(t *testing.T) {
 
    // 实例化仿冒
    testObj := new(MyMockedObject)
 
    // 设置期望,如果入参123,则Argument的第1、2元素分别置为true nil
    testObj.On("DoSomething", 123).Return(true, nil)
    // 设置期望,对于任何入参,Argument的第1、2元素分别置为true nil
    testObj.On("DoSomething", mock.Anything).Return(true, nil)
 
    // 调用仿冒方法
    b, e := testObj.DoSomething(123)
 
    assert.Equal(t, b, true)
    assert.Empty(t, e)
 
    // 断言期望
    testObj.AssertExpectations(t)
}
suite

该包提供很多面向对象语言的单元测试工具(例如Junit)提供的功能。使用此包,你可以在结构中设计自己的测试套装(testing suite),编写setup/teardown方法: 

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package testify
 
import (
    "github.com/stretchr/testify/suite"
    "testing"
)
 
// 定义测试套装
type ExampleTestSuite struct {
    suite.Suite
}
 
// 套装运行前执行
func (suite *ExampleTestSuite) SetupSuite() {
}
 
// 每个测试运行前执行
func (suite *ExampleTestSuite) SetupTest() {
}
 
// 套件结构所有Test开头的方法,都是需要执行的测试
func (suite *ExampleTestSuite) TestExample() {
}
 
// 每个测试运行后执行
func (suite *ExampleTestSuite) TearDownTest() {
}
 
// 套装运行后执行
func (suite *ExampleTestSuite) TearDownSuite() {
}
 
// 为了支持go test,需要编写一个常规的Go测试方法
func TestExampleTestSuite(t *testing.T) {
    // 并在其方法体调用:
    suite.Run(t, new(ExampleTestSuite))
}
← Previous Post
汪昌博 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 使用Mockito进行单元测试
  • Ginkgo学习笔记
  • 基于Spring Test和Mockito进行单元测试
  • Python单元测试
  • Go语言数据库编程

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • 基于Calico的CNI 27 people like this
  • Ceph学习笔记 27 people like this
  • Three.js学习笔记 24 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2