Go语言开发(二十)、GoStub测试框架

Go语言开发(二十)、GoStub测试框架

一、GoStub简介

GoStub是一款轻量级的单元测试框架,接口友好,可以对全局变量、函数或过程进行打桩。
GoStub安装:
go get github.com/prashantv/gostub

二、GoStub常用方法

gostub用于在测试时打桩变量,一旦测试运行时,重置原来的值。

type Stubs struct {
   // stubs is a map from the variable pointer (being stubbed) to the original value.
   stubs   map[reflect.Value]reflect.Value
   origEnv map[string]envVal
}

Stubs代表一系列可以重置的打桩变量。

func Stub(varToStub interface{}, stubVal interface{}) *Stubs {
   return New().Stub(varToStub, stubVal)
}

Stub使用stubVal替代存储在varToStub变量的值,返回*Stubs类型变量
varToStub必须是指向变量的指针。
stubVal是可赋值到变量的类型

func StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs {
   return New().StubFunc(funcVarToStub, stubVal...)
}

StubFunc用返回stubval值的函数替换函数变量,返回*Stubs类型变量
funcVarToStub是指向函数变量的指针。如果函数返回多个值,返回的多个值被传递给StubFunc。
func New() *Stubs
New返回用于打桩变量的*Stubs变量
func (s *Stubs) Reset()
Reset重置打桩的所有变量到其原始值
func (s *Stubs) ResetSingle(varToStub interface{})
ResetSingle重置打桩的单个变量到其原始值
func (s *Stubs) SetEnv(k, v string) *Stubs
SetEnv设置指定的环境变量到指定值
func (s *Stubs) UnsetEnv(k string) *Stubs
UnsetEnv还原指定环境变量的值
func (s *Stubs) Stub(varToStub interface{}, stubVal interface{}) *Stubs
Stub使用stubVal替代存储在varToStub变量的值
varToStub必须是指向变量的指针。
stubVal是可赋值到变量的类型
func (s *Stubs) StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs
StubFunc用返回stubval值的函数替换函数变量,返回*Stubs类型变量
funcVarToStub是指向函数变量的指针。如果函数返回多个值,返回的多个值被传递给StubFunc。

三、GoStub应用示例

1、GoStub应用场景

GoStub框架的使用场景如下:
A、为一个全局变量打桩
B、为一个函数打桩
C、为一个过程打桩
D、由任意相同或不同的基本场景组合而成

2、为全局变量打桩

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

package main

import (
   "fmt"

   "github.com/prashantv/gostub"
)

var counter = 100

func stubGlobalVariable() {
   stubs := gostub.Stub(&counter, 200)
   defer stubs.Reset()
   fmt.Println("Counter:", counter)
}

func main() {
   stubGlobalVariable()
}

// output:
// Counter: 200

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

package main

import (
   "io/ioutil"

   "fmt"

   "github.com/prashantv/gostub"
)

var configFile = "config.json"

func GetConfig() ([]byte, error) {
   return ioutil.ReadFile(configFile)
}

func stubGlobalVariable() {
   stubs := gostub.Stub(&configFile, "/tmp/test.config")
   defer stubs.Reset()
   /// 返回tmp/test.config文件的内容
   data, err := GetConfig()
   if err != nil {
      fmt.Println(err)
   }
   fmt.Println(data)
}

func main() {
   stubGlobalVariable()
}

3、为函数打桩

通常函数分为工程自定义函数与库函数。
假设工程中自定义函数如下:

func Exec(cmd string, args ...string) (string, error) {
   ...
}

Exec函数是不能通过GoStub框架打桩的。如果想要通过GoStub框架对Exec函数进行打桩,则仅需对自定义函数进行简单的重构,即将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 "test", nil
})
defer stubs.Reset()

GoStub框架专门提供了StubFunc函数用于函数打桩,对于函数的打桩代码如下:

stubs := StubFunc(&Exec,"test", nil)
defer stubs.Reset()

工程代码中会调用Golang库函数或第三方库函数,由于不能重构库函数,因此需要在工程代码中增加一层适配层,在适配层中定义库函数的变量,然后在工程代码中使用函数变量。

package Adapter

import (
   "time"

   "fmt"

   "os"

   "github.com/prashantv/gostub"
)

var timeNow = time.Now
var osHostname = os.Hostname

func getDate() int {
   return timeNow().Day()
}
func getHostName() (string, error) {
   return osHostname()
}

func StubTimeNowFunction() {
   stubs := gostub.Stub(&timeNow, func() time.Time {
      return time.Date(2015, 6, 1, 0, 0, 0, 0, time.UTC)
   })
   fmt.Println(getDate())
   defer stubs.Reset()
}

func StubHostNameFunction() {
   stubs := gostub.StubFunc(&osHostname, "LocalHost", nil)
   defer stubs.Reset()
   fmt.Println(getHostName())
}

使用示例:

package main

import "GoExample/GoStub/StubFunction"

func main() {
   Adapter.StubTimeNowFunction()
   Adapter.StubHostNameFunction()
}

4、为过程打桩

没有返回值的函数称为过程。通常将资源清理类函数定义为过程。

package main

import (
   "fmt"

   "github.com/prashantv/gostub"
)

var CleanUp = cleanUp

func cleanUp(val string) {
   fmt.Println(val)
}

func main() {
   stubs := gostub.StubFunc(&CleanUp)
   CleanUp("Hello go")
   defer stubs.Reset()
}

5、复杂场景

不论是调用Stub函数还是StubFunc函数,都会生成一个Stubs对象,Stubs对象仍然有Stub方法和StubFunc方法,所以在一个测试用例中可以同时对多个全局变量、函数或过程打桩。全局变量、函数或过程会将初始值存在一个map中,并在延迟语句中通过Reset方法统一做回滚处理。
多次打桩代码如下:

stubs := gostub.Stub(&v1, 1)
defer stubs.Reset()

// Do some testing
stubs.Stub(&v1, 5)

// More testing
stubs.Stub(&b2, 6)

多次打桩的级联表达式代码如下:
defer gostub.Stub(&v1, 1).Stub(&v2, 2).Reset()
使用GoConvey测试框架和GoStub测试框架编写的测试用例如下:

package main

import (
   "fmt"
   "testing"

   "GoExample/GoStub/StubFunction"

   "time"

   "github.com/prashantv/gostub"
   . "github.com/smartystreets/goconvey/convey"
)

var counter = 100
var CleanUp = cleanUp

func cleanUp(val string) {
   fmt.Println(val)
}

func TestFuncDemo(t *testing.T) {
   Convey("TestFuncDemo", t, func() {
      Convey("for succ", func() {
         stubs := gostub.Stub(&counter, 200)
         defer stubs.Reset()
         stubs.Stub(&Adapter.TimeNow, func() time.Time {
            return time.Date(2015, 6, 1, 0, 0, 0, 0, time.UTC)
         })
         stubs.StubFunc(&CleanUp)
         fmt.Println(counter)
         fmt.Println(Adapter.TimeNow().Day())
         CleanUp("Hello go")
      })
   })
}

6、不适用场景

GoStub框架可以解决很多场景的函数打桩问题,但下列复杂场景除外:
A、被测函数中多次调用了数据库读操作函数接口,并且数据库为key-value型。
B、被测函数中有一个循环,用于一个批量操作,当某一次操作失败,则返回失败,并进行错误处理。
C、被测函数中多次调用了同一底层操作函数。

原文地址:http://blog.51cto.com/9291927/2345604

时间: 2024-10-10 03:41:54

Go语言开发(二十)、GoStub测试框架的相关文章

Go语言开发(十二)、Go语言常用标准库二

Go语言开发(十二).Go语言常用标准库二 一.os 1.os简介 os 包提供了不依赖平台的操作系统函数接口,设计像Unix风格,但错误处理是go风格,当os包使用时,如果失败后返回错误类型而不是错误数量. 2.os常用接口 func Hostname() (name string, err error) // Hostname返回内核提供的主机名 func Environ() []string // Environ返回表示环境变量的格式为"key=value"的字符串的切片拷贝 f

Go语言开发(十五)、Go语言常用标准库五

Go语言开发(十五).Go语言常用标准库五 一.md5 1.md5简介 md5在crypto/md5包中,md5包提供了New和Sum方法. func New() hash.Hash func Sum(data []byte) [Size]byte hash.Hash继承了io.Writer,因此可以将其当成一个输入流进行内容的更新. type Writer interface { Write(p []byte) (n int, err error) } Write方法将p中的内容读入后存入到h

Go语言开发(十)、GoLand常用快捷键

Go语言开发(十).GoLand常用快捷键 一.Goland快捷键设置 GoLand支持各种编辑器的快捷键映射:File->Settings->Keymap 二.GoLand常用快捷键 1.查询快捷键 CTRL+N 查找类 CTRL+SHIFT+N 查找文件 CTRL+SHIFT+ALT+N 查找类中的方法或变量 CTRL+B 快速打开光标处的类或方法 CTRL+ALT+B 找所有的子类 CTRL+SHIFT+B 找变量的类 CTRL+G 定位行 CTRL+F 在当前窗口查找文本 CTRL+S

Go语言开发(十四)、Go语言常用标准库四

Go语言开发(十四).Go语言常用标准库四 一.heap 1.heap简介 heap仅仅提供了最小堆的操作,没有提供堆的数据结构,堆的数据结构必须由开发者自己实现.heap提供了一个heap.Interface接口来作为堆的操作和堆的数据结构(开发者自己实现)之间的桥梁,堆的数据结构必须满足此接口: type Interface interface { sort.Interface Push(x interface{}) // add x as element Len() Pop() inter

Go语言开发(十八)、Go语言MySQL数据库操作

Go语言开发(十八).Go语言MySQL数据库操作 一.MySQL数据库驱动 1.MySQL数据库驱动简介 Go语言官方没有实现MySQL数据库驱动,常用的开源MySQL数据库驱动实现如下:(1)Go MySQL DriverGo MySQL Driver支持database/sql接口,全部采用Go语言实现.官方网站:https://github.com/go-sql-driver/mysql/(2)MyMySQLMyMySQL支持database/sql接口,也支持自定义的接口,全部采用Go

测试驱动开发TDD(二)开源测试框架CppUnit

背景 CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit,是一个非常优秀的开源测试框架.CppUnit 和 JUnit 一样主要思想来源于极限编程(XProgramming).主要功能就是对单元测试进行管理,并可进行自动化测试.这样描述可能没有让您体会到测试框架的强大威力,那您在开发过程中遇到下列问题吗?如果答案是肯定的,就应该学习使用这种技术: 测试代码没有很好地维护而废弃,再次需要测试时还需要重写: 投入太多的精力,找 bug,而新的代码仍然会出现类似 bug: 写完

前端开发--实战篇之测试框架

如果不了解前端开发环境,请参考搭建前端开发环境, 如果不了解实战篇的项目配置,请参考前端开发--实战篇 步骤一:待命 在cmd里面,进入到public文件夹待命. 步骤二:初始化karma配置文件karma.conf.js 执行初始化配置文件的命令: karma init 根据向导,大多数使用默认配置即可.具体见下图: 步骤三:根据当前目录结构,修改配置文件karma.conf.js 添加待测试的js文件: files: [   'app/dist/lib/angular/*.js',   'a

python运维开发(二十五)---cmdb开发

内容目录: 浅谈ITIL CMDB介绍 Django自定义用户认证 Restful 规范 资产管理功能开发 浅谈ITIL TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部门CCTA(Central Computing and Telecommunications Agency)在20世纪80年代末制订,现由英国商务部OGC(Office of Government Commerce)负

Swift4.2语言指南(二十八) 高级运算符

除了Basic Operators中描述的运算符之外,Swift还提供了几个执行更复杂值操作的高级运算符.这些包括C和Objective-C中您熟悉的所有按位和位移运算符. 与C中的算术运算符不同,Swift中的算术运算符默认不会溢出.溢出行为被捕获并报告为错误.要选择溢出行为,请使用Swift默认溢出的第二组算术运算符,例如溢出加法运算符(&+).所有这些溢出运算符都以&符号(&)开头. 定义自己的结构,类和枚举时,为这些自定义类型提供自己的标准Swift运算符实现会很有用.Sw