go测试框架调研

Golang测试框架调研

1 Testing框架

1.1 框架介绍

// 设置代理并打开GO111MODULE 下载github包不被墙
// go env -w GOPROXY=https://goproxy.cn,direct
// go env -w GO111MODULE=on	 必须打开
// go mod init	初始化项目用go mod管理,之后 用go get后自动关联

import “testing”
//测试文件以xx_test.go命名, 通常和被测试文件(xx)放在同一个包中,
//被测试函数名以Test开头,可导出公开函数
//单元测试:验证返回结果是否预期
func TestSum(t *testing.T){

}
go test –v main_test.go main.go  

//性能测试:查看方法瓶颈,耗时,以及其它衡量指标(可查)
func BenchmarkSum(b *testing.B) {

}
go test –v –bench=“BenchmarkSum$” –run=none –cpuprifile cpu.out main_test.go main.go
go tool pprof cpu.out  //用于分析上面导出的cpu.out文件
go test -v -cover	//-v表示显示测试详细信息,-cover表示在终端查看覆盖率
go test -v ...	//用三个点来测试整个项目
go tool cover -html=cover.out -o coverage.html //生成HTML格式的覆盖率详细信息

-------------------------------------------------------
执行多文件的文件夹中单个文件测试时要带上源文件
go test -v xx_test.go xx.go	

----------------------------本地生成查看测试覆盖率--------------------
对当前生成覆盖率
 go test ./... -gcflags=-l -v -coverprofile=profile.cov
查看该文件
 go tool cover -html=profile	//注意没有后缀,会弹出到浏览器查看覆盖率

2 GoConvey 框架

2.1 框架介绍(主要用来组织测试用例)

/*
拉取项目后都要执行下面的安装
gitlab上的go版本和本地不同要执行

​ 1 go mod vendor
安装 2 go get -d github.com/smartystreets/goconvey
官网 http://goconvey.co/
So(,) 断言函数条件列表:
​ https://github.com/smartystreets/goconvey/wiki/Assertions

组织测试用例的,提供了很多断言
直接与 go test 集成
巨大的回归测试套件
可读性强的色彩控制台输出
完全自动化的 Web UI
测试代码生成器
桌面提醒(可选)
自动在终端中运行自动测试脚本
可立即在 Sublime Text 中打开测试问题对应的代码行 (some assembly required)

*/


//测试模块
//外侧集成go test 的基础上使用Convey +So()的形式执行测试用例
func TestAdd(t *testing.T){//比如对Add()函数进行测试
    
    //每一个Convey()相当于一个测试用例,并且Convey可以嵌套Convey
    Convey("用来标记当前这个测试的提示信息",t,func(){
       	//Convey函数有三个参数分别为 提示信息、传入的testing、无返回值的闭包函数
        //当前在闭包函数语句块内
  
        //首先是被测函数传递参数的初始化
        a :=1
        b :=2
        
        //初始化完毕,调用So()进行测试,
        //So(,,)的第一个参数为被测函数,第二个为断言函数,第三个为可选的和被测函数结果进行比对
        So(Add(),断言函数,)
    })
    
}

2.2 框架实践

/// --------------------------demo-------------------------
./model|
	    gofun.go	//待测试文件
		gofun_test.go	//使用goconvey自带断言函数测试
------------------------------------------------------------
//gofun.go

package model
 
import (
    "errors"
)
 
func Add(a, b int) int {
    return a + b
}
 
func Subtract(a, b int) int {
    return a - b
}
 
func Multiply(a, b int) int {
    return a * b
}
 
func Division(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("被除数不能为 0")
    }
    return a / b, nil
}
----------------------------------------------------------------------
//gofun_test.go
package model
import (
	"testing"
    . "github.com/smartystreets/goconvey/convey" // 前面加.是省略包前缀
)

//用自带的断言函数
func TestCase1(t *testing.T){
    //当前测试用例有两组一共三个测试,要都通过才能全部通过,有一个不通过也是失败
	Convey("两数相减", t, func() {
		So(Add(1, 2), ShouldEqual, 3)
	})

	//Convey的嵌套使用
	Convey("测试多个用例", t, func() {
		Convey("两数相加", func() {
			So(Add(1, 2), ShouldEqual, 3)
		})
		Convey("除数不为0", func() {
			_, err := Division(3, 0)
			So(err, ShouldNotBeNil) //返回nil测试通过
		})
	})
}

//断言函数原型
//func So(actual interface{}, assert assertion, expected ...interface{})
//type assertion func(actual interface{}, expected ...interface{}) string
/*
const (
	//
    success                = ""
    needExactValues        = "This assertion requires exactly %d comparison values (you 							 provided %d)."
    needNonEmptyCollection = "This assertion requires at least 1 comparison value (you 								provided 0)."
)
*/

//定制断言函数
func TestCase2(t *testing.T){
    Convey("定制断言判断两数相加",t,func(){
        So(Add(6,2),ShouldEqualSelf,8)
    })
}

func ShouldEqualSelf(actual interface{}, expected ...interface{}) string{
    if actual == expected[0]{	//比较两个空接口的值
        return ""
    }
   		return "The number is Notequal!" 	
}


//-------------------------------------命令行运行-----------------------
//在当前测试包所在目录下 go test -v
/*
--------------------web网页测试-------------------------------
1  在vscode中将ssh终端从cmd切换到bash
2  当前测试包执行 go test -v
3  然后执行 $GOPATH/bin/goconvey
4  浏览器输入网址 localhost:8080 查看当前测试
*/

3 GoStub 框架

3.1 框架介绍(主要用来对函数、全局变量打桩)

/*
description
	GoStub框架,接口友好,可以对全局变量、函数或过程打桩(桩是未实现的关联函数,提供一个返回值)
	给函数打桩时,需要做侵入式修改
	实现测试中的隔离(A-B-C)、补齐(未实现的函数)、控制

安装 go get -d github.com/prashantv/gostub
*/

3.2 框架实践

/// --------------------------demo1--------------------
//---------------------为全局变量、函数、过程打桩--------------------
package main
import (
	"fmt"
    ."github.com/prashantv/gostub"	//gostub.Stub()在前面加.号后直接Stub()
)
var num = 200 

//1.打桩全局变量
func stubGlobalVar(){	
    // 通过测试过程的打桩,在测试的时候使用的值测试完毕恢复,不影响原来的定义
    // 也就是测试全局变量的值设定
    // Stub(para1,para2)	//para1是变量指针,para2是桩值(具体的值,或者匿名函数)
    fmt.Println(num)  //输出200
    stubs := Stub(&num,100)
    defer stubs.Reset()	// 函数运行完毕恢复全局变S量的值
    fmt.Println(num)	//输出100
    stubs.Stub(&num,300)	//可以多次打桩,打桩改变桩值
    fmt.Println(num)	//输出 300
}

//2.打桩函数,库函数也是如此打桩
func ComplexAdd(a int,b int) int{
    //假设本函数的加法有更复杂的实现但还没有实现,测试的时候指定最简单的一种加法来打桩
    return a*10 +b*100	
}

func stubComplexAdd(){
    //Stub()的第一个参数必须是指针变量,因此先建立函数变量,然后传递,不能直接传函数
    // 错位示范 Stub(&ComplexAdd,闭包或者匿名函数)其中第一个参数错误
    fmt.Println(ComplexAdd(1,2))	//输出210
    var ComplexAddFun = ComplexAdd	//函数变量ComplexAddFun,原函数ComplexAdd
    stubs := Stub(&ComplexAddFun,func(a int,b int)int{
        //该闭包函数的函数头和返回值要按照被打桩的函数格式
        return a+b	//只是预先设定了ComplexAdd的返回值
    })
    //在现在的定义后,ComplexAdd的函数内容已经被打桩实现
    defer stubs.Reset()	//stubComplexAdd()调用完毕恢复原函数
    fmt.Println(ComplexAdd(1,2))	//输出210 ,打桩只是给函数变量打桩,原函数没变
    fmt.Println(ComplexAddFun(1,2))	//输出3
    //StubFunc(&函数变量,一个固定的返回值)	将原本的函数实现为返回固定值
    stubs.StubFunc(&ComplexAddFun,10)
    fmt.Println(ComplexAddFun(1,2))	//10
}

//3.为过程打桩,过程就是无返回值的函数
func Process(str string){
    fmt.Printf("你好"+str)
}

func stubProcess(){
    var ProcessFun = Process	
    stubs := StubFunc(&ProcessFun)
    defer stubs.Reset()
    ProcessFun("hhh")
}

//无论是Stub还是StubFunc都会返回stubs对象,这个对象还可以调用Stub和StubFunc方法
//也就是一个测试用例中的stubs对象可以一直使用

func main(){
    stubGlobalVar()
    stubComplexAdd()
}

/// --------------------------demo2--------------------
//---------------------Stub和Convey的结合使用--------------------
//  测试被测文件中的全局变量、桩函数、库函数的使用
model_01|
		stubUse.go
		stubUse_test.go
-----------------------------------------------------------------
//stubUse.go
package model_01
import (
	"time"
)
var Num = 100	//打桩来测试全局变量

func ComplexAdd(a int,b int)int{
    return a*10+b*100	//一种复杂的加法运算,假设暂时未完全实现,用打桩来测试连接
}

------------------------------------------------------------------
//stubUse_test.go
package model_01
import (
	"testing"
    . "github.com/smartystreets/goconvey/convey"
    ."github.com/prashantv/gostub"
)
func TestAll(t *testing.T){
    Convey("convey和stub联合",t,func(){
        Convey("打桩全局变量",func(){
            //无参,无返回值的闭包
            stubs := Stub(&Num,100)
            defer stubs.Reset()
            So(Num,ShouldEqual,100)	
        })
        Convey("打桩函数",func(){
            var ComplexAddFunc = ComplexAdd	//定义函数变量
            stubs := Stub(&ComplexAddFunc,func(a int,b int)int{
                return a+b
            })
            So(ComplexAddFunc(1,2),ShouldEqual,3)
        })
   		Convey("非打桩函数", func() {
			So(ComplexAdd(1, 2), ShouldEqual, 3)
		})
    })
}

4 GoMock 框架

4.1 框架介绍(主要用来对接口打桩)

/*
description
	GoMock 主要用来给接口打桩的。 mockgen 可以生成对应的接口测试文件
// 安装两个工具 
// go get github.com/golang/mock/gomock 	库
// go get github.com/golang/mock/mockgen 	代码辅助生产工具

mockgen有两种操作模式:源文件和反射
	(下面的xxx.go主要是定义接口的go代码)
	mockgen -source=xxxx.go > mock/mock_xxxx.go 
	后半句重定向不指定的话会自动生成该文件中接口的名字
	
	反射模式通过构建一个程序用反射理解接口生成一个mock类文件,通过两个非标志参数开启:导入路径和用逗号	   分隔的符号列表(多个interface)

	mockgen packagepath Interface1,Interface2...
    第一个参数是基于GOPATH的相对路径,第二个参数可以为多个interface,并且interface之间只能用逗号分		隔,不能有空格

    对于复杂场景,如一个源文件定义了多个interface而只想对部分interface进行mock,或者interface存在	  嵌套,则需要使用反射模式

*/

4.2 框架实践

/// --------------------------demo1 用源文件的方式生成gomock--------------------
hellomock|
		 hellomock.go//这个就是接口类,要用gomock来测试
		 person.go	//Person实现了上面的接口
		 Company.go //有方法的参数是hellomock.go的接口
----------------------------------------------------------------------
//hellomock.go
package hellomock

type Talker interface {	//对该文件的接口及接口里的函数进行mock
	SayHello(word string) (response string)
}
--------------------------------------------------------------------
//person.go
package hellomock

import "fmt"

type Person struct { //实现了接口
	name string
}

func NewPerson(name string) *Person {
	return &Person{name: name}
}
func (p *Person) SayHello(name string) (word string) {
	return fmt.Sprintf("hello %s, %s", name, p.name)
}
-------------------------------------------------------------------
//Company.go
package hellomock

type Company struct {
	Usher Talker
}

func NewCompany(t Talker) *Company {
	return &Company{
		Usher: t,
	}
}

func (c *Company) Meeting(guestName string) string {
	return c.Usher.SayHello(guestName)
}
-------------------------------------------------------------------
//普通的测试方式  需要先生成一个真实的对象
func TestNormal(t *testing.T) {
	c := NewPerson("xiaobai") //c实现了接口
	company := NewCompany(c)  //NewCompany的参数是接口类
	t.Log(company.Meeting("说话"))
}
------------------------------------------------------------------
/*
用gomock的工具mockgen实现测试
1.在bash中先生成mock对象
$GOPATH/bin/mockgen -souce=hellomock.go > mock/mock_Talker.go
2.使用mock_Talker进行gomock
*/

5 Monkey 框架

5.1 框架介绍(主要用来给成员函数也就是方法打桩的)

//参考网站 (两个都要看)
// https://www.jianshu.com/p/2f675d5e334e
// https://www.jianshu.com/p/633b55d73ddd
安装
go get -d github.com/agiledragon/gomonkey
go mod vendor

支持为一个函数打一个桩
支持为一个成员方法打一个桩
支持为一个全局变量打一个桩
支持为一个函数变量打一个桩
支持为一个函数打一个特定的桩序列
支持为一个成员方法打一个特定的桩序列
支持为一个函数变量打一个特定的桩序列

对于被测函数如果只有一条语句,会出现inline情况,打桩失败,要在终端调用语句中加入 -gcflags=-l禁止inline
go test -gcflags=-l -v xx_test.go xx.go

打桩目标是否为内联的函数或成员方法?如果是,请在测试时通过命令行参数 -gcflags=-l (go1.10 版本之前)或-gcflags=all=-l(go1.10 版本及之后)关闭内联优化;

对于golang版本在1.6及以上,如果被测函数首字母小写会报panic,做法是不对非公有函数测试

//---------------------函数API--------------------------------------


//1 为函数打桩,ApplyFunc 第一个参数是函数名,第二个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
func ApplyFunc(target, double interface{}) *Patches
func (this *Patches) ApplyFunc(target, double interface{}) *Patches


//2 为方法打桩,ApplyMethod 第一个参数是目标类的指针变量的反射类型,第二个参数是字符串形式的方法名,第三个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
//比如Person类的Add方法打桩
// var p *Person :ApplyMethod(reflect.TypeOf(p), "Add", 匿名装函数)
func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches
func (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches

//3 为全局变量打桩,ApplyGlobalVar 第一个参数是全局变量的地址,第二个参数是全局变量的桩。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
func ApplyGlobalVar(target, double interface{}) *Patches	//注意target传地址
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches


//4 为函数变量打桩,ApplyFuncVar 第一个参数是函数变量的地址,第二个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
func ApplyFuncVar(target, double interface{}) *Patches
func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches


5.2 框架实践

/// --------------------------demo--------------------

//--------------------sona1.go----------------------------------------------------
package sonar

var Num = 200 //打桩来测试全局变量

func ComplexAdd(a int, b int) int {
return a10 + b100 //一种复杂的加法运算,假设暂时未完全实现,用打桩来测试连接
}

func AAA(a, b int) int {
return ComplexAdd(a, b)
}

//-----------------sonar1_test.go---------------------------------------------
package sonar

import (
“fmt”
“testing”
. “github.com/agiledragon/gomonkey”
. “github.com/smartystreets/goconvey/convey”
)

func TestAll(t *testing.T) {
Convey(“convey和monkey联合”, t, func() {
Convey(“打桩全局变量\n”,func(){
patches := ApplyGlobalVar(&WorkQueues, 100)
defer patches.Reset()
fmt.Println(num) //输出num为100
})

	Convey("打桩函数\n", func() {
		fmt.Println(AAA(1, 2)) //这里是原函数输出 210
		patches := ApplyFunc(ComplexAdd, func(a int, b int) int {	//原函数前面不加&
			return a + b
		})
		defer patches.Reset()
		//现在对ComplexAdd函数打桩
		fmt.Println(AAA(1, 2)) //这里是打桩输出 3
		So(AAA(1, 2), ShouldEqual, 3)
	})
    
})

}
//-----------------------------在当前包下执行指令-----------------------------

  • 注意ComplexAdd函数只有一行语句,打桩要加-gcflags=-l,否则打桩失败
    go test -gcflags=-l -v sonar1_test.go

## 6.sonar 加入测试覆盖率

### 6.1相关网站


https://docs.sonarqube.org/latest/analysis/coverage/	  sonar和覆盖率官方指导
https://community.sonarsource.com/c/announce/guides/22	   sonar官方社区
https://blog.csdn.net/baidu_36943075/article/details/90634404    goTest使用指导
https://blog.csdn.net/baidu_36943075/article/details/90634160	sonar集成golang检测


### 6.2流程

0.首先sonar不负责执行测试,只是根据测试报告和覆盖率报告来检测,下面是如何生成扫描报告

1.先在gitlab-ci.yml文件中配置sonar,指定覆盖率和测试的文件夹,并过滤扫描*_test.go这类文件

   其中scanner中的  -Dsonar. 代替 sonar.

2.在项目待测试目录执行 go test -v -json > ./report/reportTest/test.json 进行全局的测试
	test.json对应上图第二个红框配置

3.在项目待测试目录目录执行 go test -v -coverprofile=./report/reportCover/covprofile	生成的		      covprofile文件是覆盖率测试报告。

*.再任意目录执行测试覆盖率,要指定生成文件位置在sonar的配置位置        go test -coverprofile=/c/xx/software/golang_08/go_code/src/test_sonar/report/reportCover/profile.out

4.push后,等待更新扫描完成,到sonarqube中可以看到指定分支有代码测试覆盖率出现。

5.对于打桩不会有测试覆盖。

6.父目录下测试其子目录的所有测试覆盖率  (./...表示当前目录下所有子目录)

```shell
go test ./... -     coverprofile=/c/xx/software/golang_08/go_code/src/test_sonar/report/reportCover/profile.out

7.调研测集成试增量覆盖率

tips:
//拉取项目后都要执行测试框架的安装
// gitlab上的go版本和本地不同要执行 go mod vendor
// 安装 go get github.com/smartystreets/goconvey
// go test -v -cover //-v表示当前路径所有文件,-cover表示在终端查看覆盖率
// go test -v //本地进行测试,输出结果到终端
go test -coverprofile=covprofile 对当前目录下进行测试覆盖率生成covprofile文件
go test -v -cover //-v表示显示测试详细信息,-cover表示在终端查看覆盖率
go test -v … //用三个点来表示测试整个项目
go tool cover -html=cover.out -o coverage.html //生成HTML格式的覆盖率详细信息

6.3覆盖率有效的配置文件


## 7.本地-gitlab-服务器同步进行测试流程

1.在本地将分支切换到要同步的分支(dev_lq),进行pull	git pull origin dev_	这样保证最新版本
2.在本地编写测试用例,(可能要安装引用goconvey和gostub包(go mod vendor  go get -d xx)),编写完毕
  包安装指令:
	govendor fetch github.com/smartystreets/goconvey
	govendor fetch github.com/agiledragon/gomonkey

	go get -d github.com/smartystreets/goconvey
	go get -d github.com/agiledragon/gomonkey
	go mod vendor
	go test -gcflags -v sonar1_test.go
3.更新本地仓库 git add .	  git commit -m "feat(qi.liang):add"
4.推送到远程同名分支 git push -u origin dev_lq
5.进入到服务器docker中
6.docker ps	//显示为某种镜像创建的某个容器 的具体信息包括id
  docker exec -it xxxx bash	//通过bash启动id为:xxxx 的容器
7.在容器中同步拉取分支(要进到gopath的src目录下)
	git clone https://gitxxx.git
	(输入账号密码)
	git checkout ww	从master切换到dev_lq分支
	或者新建分支	
	git checkout -b dev__v1
	同步子库
	git submodule init && git submodule update
	go mod vendor

8.在容器中定位到要生成覆盖率的文件夹下面
  go test ./... -gcflags=-l -v -coverprofile=/xx/profile.out
  上面的语句对当前文件夹下面的所有子文件夹里的_test.go进行测试并在项目根目录的-report生成覆盖率报告
  具体覆盖率文件名字要和gitlab-ci.yml配置中相同。
   
  只执行单个包的单元测试用下面的语句:
  go test -v worker_test.go worker.go xx.go	
  上面的语句要将worker_test.go源文件worker.go和引用的相关包都要加到命令行,否则报未定义
9.生成覆盖率文件完毕,要进行git
   到项目根目录
   git add .
   git commit -m "feat(xx):add"
   git push -u origin xx//注意要本地当前分支和远程要push的分支要同名
10.提示文件不存在
	git submodule init && git submodule update 项目中的子文件要用这条语句同步
11.删除git-lab远程分支指令
	git push origin --delete dev_
12.远程上库 go 环境设置
	GOSUMDB="off"

7.本地-gitlab-服务器同步进行测试流程
1.在本地将分支切换到要同步的分支(dev_),进行pull git pull origin dev_ 这样保证最新版本
2.在本地编写测试用例,(可能要安装引用goconvey和gostub包(go mod vendor go get -d xx)),编写完毕
包安装指令:
govendor fetch github.com/smartystreets/goconvey
govendor fetch github.com/agiledragon/gomonkey

go get -d github.com/smartystreets/goconvey
go get -d github.com/agiledragon/gomonkey
go mod vendor
go test -gcflags -v sonar1_test.go

3.更新本地仓库 git add . git commit -m “feat(qi.liang):add”
4.推送到远程同名分支 git push -u origin dev_lq
5.进入到服务器docker中
6.docker ps //显示为某种镜像创建的某个容器 的具体信息包括id
docker exec -it xxxx bash //通过bash启动id为:xxxx 的容器
7.在容器中同步拉取分支(要进到gopath的src目录下)
git clone xxx
(输入账号密码)
git checkout dev_ 从master切换到dev_分支
或者新建分支
git checkout -b dev__v1
同步子库
git submodule init && git submodule update
go mod vendor

8.在容器中定位到要生成覆盖率的文件夹下面
go test ./… -gcflags=-l -v -coverprofile=/root/go/src/xx/report/reportCover/profile.out
上面的语句对当前文件夹下面的所有子文件夹里的_test.go进行测试并在项目根目录的-report生成覆盖率报告
具体覆盖率文件名字要和gitlab-ci.yml配置中相同。

只执行单个包的单元测试用下面的语句:
go test -v worker_test.go worker.go xx.go
上面的语句要将worker_test.go源文件worker.go和引用的相关包都要加到命令行,否则报未定义
9.生成覆盖率文件完毕,要进行git
到项目根目录
git add .
git commit -m “feat():add”
git push -u origin dev_ //注意要本地当前分支和远程要push的分支要同名
10.提示文件不存在
git submodule init && git submodule update 项目中的子文件要用这条语句同步
11.删除git-lab远程分支指令
git push origin --delete dev_
12.远程上库 go 环境设置
GOSUMDB=“off”

14.docker拉取镜像并后台开启
docker pull images
docker images 显示所有镜像
docker run --name new_name -d images 返回容器ID
docker ps 显示所有后台容器
docker ps -a 显示所有包括未运行的容器
docker rm container_id 删除指定id的容器
docker stop container_id 停止指定容器
docker start container_id 开启指定容器
docker exec -it container_id bash 运行指定的容器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值