Go Convey测试框架入门(go convey gomonkey)

news2024/11/15 19:31:34

Go Convey测试框架入门

介绍

GoConvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性。
Golang虽然自带了单元测试功能,并且在GoConvey框架诞生之前也出现了许多第三方测试框架,但没有一个测试框架像GoConvey一样能够让程序员如此简洁优雅的编写测试代码。

官网:http://smartystreets.github.io/goconvey/

安装

go get 方式安装(go path时代,关闭go mod)

//下载源码并执行go build
//源码下载到$GOPATH/src目录
//go build之后的结果到$GOPATH/bin
go get github.com/smartystreets/goconvey

在$GOPATH/src目录下新增了github.com子目录,该子目录里包含了GoConvey框架的库代码 在$GOPATH/bin目录下新增了GoConvey框架的可执行程序goconvey

go install方式(开启go mod)

在gomod时代一般不需要先显式安装(gomod机制会自动从goproxy拉取依赖到本地cache),只需要go.mod中引入,然后go mod tidy即可。除非要使用GoConvey的web界面,这时需要提前安装GoConvey的二进制,命令为go install github.com/smartystreets/goconvey@latest。

go.mod:

module ziyi.convey.com

go 1.19

require github.com/smartystreets/goconvey v1.8.1

require (
	github.com/gopherjs/gopherjs v1.17.2 // indirect
	github.com/jtolds/gls v4.20.0+incompatible // indirect
	github.com/smarty/assertions v1.15.0 // indirect
)
//我后面要演示web页面,因此这里显示安装一下
//安装之后,依然会将编译好的二进制放在$GOPATH/bin
go install github.com/smartystreets/goconvey@latest

我的GOPATH为E:\Go\GoPro:
在这里插入图片描述

使用

注意事项

  1. import goconvey包时,前面加点号".",以减少冗余的代码。凡是在测试代码中看到Convey和So两个方法,肯定是convey包的。我们需要注意不要在产品代码中定义相同的函数名
  2. 测试函数的名字必须以Test开头,而且参数类型必须为*testing.T
  3. 每个测试用例必须使用Convey函数包裹起来,它的第一个参数为string类型的测试描述,第二个参数为测试函数的入参(类型为*testing.T),第三个参数为不接收任何参数也不返回任何值的函数(习惯使用闭包)
  4. Convey函数的第三个参数闭包的实现中通过So函数完成断言判断,它的第一个参数为实际值,第二个参数为断言函数变量,第三个参数或者没有(当第二个参数为类ShouldBeTrue形式的函数变量)或者有(当第二个函数为类ShouldEqual形式的函数变量)

案例一:1个测试用例1个Convey

c_test.go:

package main

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

func TestEqualWithSingleTestCase(t *testing.T) {
	// test name:用例名称
	// t:需要传入*testing.T
	// func(){} 测试函数
	Convey("test name", t, func() {
		//1+1:断言
		//ShouldEqual:convey内置的断言
		//2:期望结果
		So(1+1, ShouldEqual, 2)
	})
}

在这里插入图片描述

案例二:多个Convey多个测试用例

①平铺写法

func TestEqualWithMultipleTestCase(t *testing.T) {
	Convey("test add case", t, func() {
		So(1+1, ShouldEqual, 2)
	})
	Convey("test sub case", t, func() {
		So(1-1, ShouldEqual, 0)
	})
	Convey("test multi case", t, func() {
		So(1*1, ShouldNotEqual, -1)
	})
}

②Convey嵌套写法

只需要最外层的Convey传t *testing.T即可

func TestEqualWithMultipleTestCaseAndNested(t *testing.T) {
	Convey("test case", t, func() {
		Convey("test add case", func() {
			So(1+1, ShouldEqual, 2)
		})
		Convey("test sub case", func() {
			So(1-1, ShouldEqual, 0)
		})
		Convey("test multi case", func() {
			So(1*1, ShouldNotEqual, -1)
		})
	})
}

案例三:"函数式"断言(assert传入函数)

// 案例三:函数式断言
func TestFunctionalAssertion(t *testing.T) {
	Convey("test case", t, func() {
		So(add(1, 1), ShouldEqual, 2)
	})
}

func add(a, b int) int {
	return a + b
}

案例四:忽略部分Convey断言

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

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

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

当测试代码中存在SkipConvey时,相应闭包函数中不管是否为SkipSo,都将被忽略。
不管存在SkipConvey还是SkipSo时,测试日志中都有字符串"{n} total assertions (one or more sections skipped)",其中{n}表示测试中实际已运行的断言语句数

// 案例四:忽略Convey断言
//忽略所有断言
func TestCaseSkipConvey(t *testing.T) {
	SkipConvey("test case", t, func() {
		So(add(1, 1), ShouldEqual, 2)
	})
}

//忽略某些断言(SkipSo的断言将被忽略)
func TestCaseSkipSo(t *testing.T) {
	Convey("test case", t, func() {
		SkipSo(add(1, 1), ShouldEqual, 2)
		So(1-1, ShouldEqual, 0)
	})
}

比如我执行TestCaseSkipSo函数:
在这里插入图片描述

案例五:定制断言函数

①原理分析

  1. 查看So源码
func So(actual interface{}, assert Assertion, expected ...any)

在这里插入图片描述
2. 点击查看Assertion结构体

type Assertion func(actual any, expected ...any) string

当Assertion的变量的返回值为""时表示断言成功,否则表示失败:

const assertionSuccess = ""

在这里插入图片描述
3. 结论
因此我们只需要按照结构,实现func,最后返回空串代表断言成功,否则失败

②实际操作

从上面的分析我们可以知道,当Assertion最后返回""代表断言成功,反之失败

// 案例五:定制断言
func TestCustomAssertion(t *testing.T) {
	Convey("test custom assert", t, func() {
		So(1+1, CustomAssertionWithRaiseMoney, 2)
	})
}

func CustomAssertionWithRaiseMoney(actual any, expected ...any) string {
	if actual == expected[0] {
		return ""
	} else {
		return "doesn't raise money"
	}
}

案例六:访问web页面

如果要访问web页面,需要有goconvey.exe程序并运行,同时需要将xxx_test.go文件放在与exe同目录,否则无法识别到

  • go get “github.com/smartystreets/goconvey”
  • go install “github.com/smartystreets/goconvey” (如果开启了go mod,则用该方式)

默认安装到$GOPATH/bin目录下

执行exe后,访问本地localhost:8080端口即可看到web页面,页面展示的了单测的通过情况等。
在这里插入图片描述

拓展:gomonkey(打桩工具)

如果在被测函数中调用了其他函数(比如其他业务方的),可以使用以下方法,gomonkey打桩工具

  • 官网:https://github.com/agiledragon/gomonkey
//安装依赖
go get "github.com/agiledragon/gomonkey"

给全局变量打桩

使用 gomonkey 可以方便地模拟函数的行为


// 拓展:配合monkey打桩
var num = 10 //全局变量

func TestApplyGlobalVar(t *testing.T) {
	Convey("TestApplyGlobalVar", t, func() {
		Convey("change", func() {
			//模拟函数行为,给全局变量复制,在函数结束后直接通过reset恢复全局变量值
			patches := gomonkey.ApplyGlobalVar(&num, 150)
			defer patches.Reset()
			So(num, ShouldEqual, 150)
		})

		Convey("recover", func() {
			So(num, ShouldEqual, 10)
		})
	})
}

给函数打桩

//对函数进行打桩
func TestFunc(t *testing.T) {
	// mock 了 networkCompute(),返回了计算结果2
	patches := gomonkey.ApplyFunc(networkCompute, func(a, b int) (int, error) {
		return 2, nil
	})
	defer patches.Reset()
	sum, err := Compute(1, 2)
	println("expected %v, got %v", 2, sum)
	if sum != 2 || err != nil {
		t.Errorf("expected %v, got %v", 2, sum)
	}
}

func networkCompute(a, b int) (int, error) {
	// do something in remote computer
	c := a + b
	return c, nil
}

func Compute(a, b int) (int, error) {
	sum, err := networkCompute(a, b)
	return sum, err
}

全部代码:

欢迎大家star在这里插入图片描述

  • Github:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-convey
    在这里插入图片描述

c_test.go:

package main

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

// 案例一:一个Convey,一个用例
func TestEqualWithSingleTestCase(t *testing.T) {
	// test name:用例名称
	// t:需要传入*testing.T
	// func(){} 测试函数
	Convey("test name", t, func() {
		//1+1:断言
		//ShouldEqual:convey内置的断言
		//2:期望结果
		So(1+1, ShouldEqual, 2)
	})
}

// 案例二:多个Convey,多个用例(平铺写法)
func TestEqualWithMultipleTestCase(t *testing.T) {
	Convey("test add case", t, func() {
		So(1+1, ShouldEqual, 2)
	})
	Convey("test sub case", t, func() {
		So(1-1, ShouldEqual, 0)
	})
	Convey("test multi case", t, func() {
		So(1*1, ShouldNotEqual, -1)
	})
}

// 案例二:多个Convey,多个用例(嵌套写法)
func TestEqualWithMultipleTestCaseAndNested(t *testing.T) {
	Convey("test case", t, func() {
		Convey("test add case", func() {
			So(1+1, ShouldEqual, 2)
		})
		Convey("test sub case", func() {
			So(1-1, ShouldEqual, 0)
		})
		Convey("test multi case", func() {
			So(1*1, ShouldNotEqual, -1)
		})
	})
}

// 案例三:函数式断言
func TestFunctionalAssertion(t *testing.T) {
	Convey("test case", t, func() {
		So(add(1, 1), ShouldEqual, 2)
	})
}

func add(a, b int) int {
	return a + b
}

// 案例四:忽略Convey断言
// 忽略所有断言
func TestCaseSkipConvey(t *testing.T) {
	SkipConvey("test case", t, func() {
		So(add(1, 1), ShouldEqual, 2)
	})
}

// 忽略某些断言(SkipSo的断言将被忽略)
func TestCaseSkipSo(t *testing.T) {
	Convey("test case", t, func() {
		SkipSo(add(1, 1), ShouldEqual, 2)
		So(1-1, ShouldEqual, 0)
	})
}

// 案例五:定制断言
func TestCustomAssertion(t *testing.T) {
	Convey("test custom assert", t, func() {
		So(1+1, CustomAssertionWithRaiseMoney, 2)
	})
}

func CustomAssertionWithRaiseMoney(actual any, expected ...any) string {
	if actual == expected[0] {
		return ""
	} else {
		return "doesn't raise money"
	}
}

// 拓展:配合monkey打桩
var num = 10 //全局变量

func TestApplyGlobalVar(t *testing.T) {
	Convey("TestApplyGlobalVar", t, func() {
		Convey("change", func() {
			//模拟函数行为,给全局变量复制,在函数结束后直接通过reset恢复全局变量值
			patches := gomonkey.ApplyGlobalVar(&num, 150)
			defer patches.Reset()
			So(num, ShouldEqual, 150)
		})

		Convey("recover", func() {
			So(num, ShouldEqual, 10)
		})
	})
}

// 对函数进行打桩
func TestFunc(t *testing.T) {
	// mock 了 networkCompute(),返回了计算结果2
	patches := gomonkey.ApplyFunc(networkCompute, func(a, b int) (int, error) {
		return 2, nil
	})
	defer patches.Reset()
	sum, err := Compute(1, 2)
	println("expected %v, got %v", 2, sum)
	if sum != 2 || err != nil {
		t.Errorf("expected %v, got %v", 2, sum)
	}
}

func networkCompute(a, b int) (int, error) {
	// do something in remote computer
	c := a + b
	return c, nil
}

func Compute(a, b int) (int, error) {
	sum, err := networkCompute(a, b)
	return sum, err
}

go.mod:

module ziyi.convey.com

go 1.19

require (
	github.com/agiledragon/gomonkey v2.0.2+incompatible
	github.com/smartystreets/goconvey v1.8.1
)

require (
	github.com/gopherjs/gopherjs v1.17.2 // indirect
	github.com/jtolds/gls v4.20.0+incompatible // indirect
	github.com/smarty/assertions v1.15.0 // indirect
)

参考文章:
http://smartystreets.github.io/goconvey/
https://www.jianshu.com/p/e3b2b1194830

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2068272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA后端程序拉取私人仓库的npm包并将该程序打包成jar包

当前有一个系统用于导出项目,而每次导出的项目并不可以直接使用,需要手动从npm私人仓库中获取一个npm包然后将他们整合到一起它才是一个完整的项目,所以目前我的任务就是编写一个java程序可以自动地从npm私人仓库中拉取下来那个模板代码到指定…

虚拟机网络的三种模式,NAT模式,桥接模式,仅主机模式

一、首先说最简单的也就是桥接模式 使用桥接模式会在虚拟机生成一个虚拟交换机,连接到主机的网卡,所以他们是能互相ping通的。 二、NAT模式,我感觉是最复杂的一个模式 使用nat模式,主机会多出一个网卡,这个网卡vmnet…

微信小程序获取当前位置并自定义浮窗

1、在腾讯地图api申请key(添加微信小程序的appid)。 每个Key每日可以免费使用100次,超过次数后会导致地图不显示。可以多申请几个Key解决。WebService API | 腾讯位置服务腾讯地图开放平台为各类应用厂商和开发者提供基于腾讯地图的地理位置…

推荐一个国内Midjourney镜像站,限时充值享5折优惠 结尾附实测图片

作为一名绘画爱好者,你是否曾梦想过将脑海中的画面转化为现实?现在,有了群嘉智创平台(ai.qunzjia.cn),这一切都将成为可能。群嘉智创是国内领先的AI对话与Midjourney绘画服务平台,通过接入国内多…

如何使用ssm实现校园美食交流系统+vue

TOC ssm026校园美食交流系统vue 第1章 概述 1.1 研究背景 随着现代网络技术发展,对于校园美食交流系统现在正处于网络发展的阶段,所以对它的要求也是比较严格的,要从这个系统的功能和用户实际需求来进行对系统制定开发的发展方式&#xf…

【MySQL】 黑马 MySQL进阶 笔记

文章目录 存储引擎MySQL的体系结构存储引擎概念存储引擎特点InnoDBMyISAMMemory 存储引擎选择 索引概述结构B Tree(多路平衡查找树)B TreeHash为什么InnoDB存储引擎选择使用Btree索引结构? 分类思考题 语法SQL性能分析(索引相关)SQL执行频率慢查询日志p…

VMware Workstation Pro 下载

文章目录 VMware Workstation ProVMware下载与安装 VMware Workstation Pro VMware Workstation Pro 对个人用户已经完全免费! VMware下载与安装 第一步:进入vmware的官网 VMWare已被收购,因此它会跳到, Broadcom 注册页面&…

[Meachines] [Easy] granny IIS 6.0+CVE-2017-7269+进程迁移+MS15-051权限提升

信息收集 IP AddressOpening Ports10.10.10.15TCP:80 $ nmap -p- 10.10.10.15 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 80/tcp open http Microsoft IIS httpd 6.0 |_http-server-header: Microsoft-IIS/6.0 | http-methods: |_ Potentially risky…

移动式气象站:科技赋能,监测天气

在自然灾害频发、气候变化日益显著的今天,准确、及时地获取气象信息对于农业生产、城市规划、交通运输以及灾害预警等领域至关重要。传统固定气象站虽能提供稳定的观测数据,但在偏远地区、灾害现场或快速变化的环境中,其局限性逐渐显现。为此…

怎么都在劝我用通义灵码

听朋友说最近通义灵码有个活动,分享体验心得就有机会抽 iPhone 15。而且通过活动第一次使用通义灵码的新用户,还人均送一个“显眼包”。 有点儿心动了。点开活动页面一看,好家伙,好几百人都在劝我用通义灵码。 来看看他们是怎么说…

Winxvideo AI(AI视频编辑软件) v3.5 中文免安装版

Winxvideo AI是一款基于人工智能技术开发的视频编辑软件。 软件截图: 使用说明: 解压后,双击start_xvideo.bat来运行软件 下载地址:压缩包 解压密码:helloh 下载时可能会有广告,忽略,等下载…

深入学习SQL优化的第三天

目录 聚合函数 排序和分组 聚合函数 1251. 平均售价 表:Prices------------------------ | Column Name | Type | ------------------------ | product_id | int | | start_date | date | | end_date | date | | price | int …

【题解】【排序】—— [NOIP1998 提高组] 拼数

【题解】【排序】—— [NOIP1998 提高组] 拼数 [NOIP1998 提高组] 拼数题目描述输入格式输出格式输入输出样例输入 #1输出 #1输入 #2输出 #2 提示 1.题意解析2.AC代码 [NOIP1998 提高组] 拼数 题目描述 设有 n n n 个正整数 a 1 … a n a_1 \dots a_n a1​…an​&#xff0…

第41篇 使用数码管实现计数器<二>

Q:如何设计汇编语言程序实现手动控制计数器? A:在本实验程序中,使用轮询法读取Data寄存器获取KEY的状态,当未按下任何KEY时,Data寄存器中的值为0,当按下按键KEY[i]时,Data寄存器中…

Circuitjs 创建自定义逻辑(Custom Logic)器件

您可以使用 自定义逻辑芯片 来实现自己的简单逻辑器件. 位于“菜单–绘制–数字芯片–添加自定义逻辑”下, 或者是"右键–数字芯片–添加自定义逻辑". 视频简介: Circuitjs 自定义逻辑电路(custom logic)功能简介 一个具体示例 来看一个具体的示例, 通过它来讲述 自…

【案例56】安全设备导致请求被拦截

问题现象 访问相关报表 第二次访问发现有相关的连接问题 问题分析 服务器访问相关节点,发现相关节点无此问题。从客户的客户端访问缺有问题。在nclog中发现如下日志,链接被重置。 直接访问服务器无丢包现象。客户端未开防火墙。装了杀毒软件已经卸载。…

vue3 实现历史步骤记录

通过vueuse中的 useManualRefHistory,快速实现历史操作记录 所需环境: vue 实现目标 历史记录撤销重做记录覆盖 代码原理 把键盘监听事件挂载在document上,当键盘事件发生时,依次匹配事先订阅的快捷键/单按键事件&#xff0c…

在定义的接口前加前缀路径

前因 在一个服务中,既定义了app端接口,又定义了pc端接口,为了方便区分,可以在项目里建立一个名为"app"、"pc"的文件夹,分别为app、pc提供接口。当app和pc接口一致时,写完一端接口后&a…

Axure设计之动态条形图教程(中继器)

在Axure RP中,中继器是一个非常强大的工具,它允许我们动态地展示数据,并且可以轻松实现复杂的交互效果。本文将详细介绍如何使用中继器来制作一个动态条形图,并展示其在实际项目中的应用。 一、效果预览 预览地址:http…

BC131 矩阵相等判定(c语言)

1.描述 :KiKi得到了两个n行m列的矩阵,他想知道两个矩阵是否相等,请你回答他。 (当两个矩阵对应数组元素都相等时两个矩阵相等)。 // //输入描述: //第一行包含两个整数n和m,表示两个矩阵包含n行m列,用空格分隔。 // //…