技术标签: # Gin # Golang golang 编程语言 单元测试 go
昨天晚上在学习慕课网的课程时,写了个简单的抽奖demo,打算简单测试在并发场景下临界资源是否被修改的问题。
然后前后折腾了好久才测试成功,记录下自己在进行httptest
单元测试时学到的知识。
以下代码是要的测试内容,大致有三个功能:
记得初始化锁,否则不起作用。
// 用户列表 共享变量(临界资源)
var userList []string
// gin引擎
var router *gin.Engine
// 互斥锁
var mux sync.Mutex
func main() {
router.Run(":8080")
}
主要初始化了三个功能的路由
func init() {
router = gin.Default()
// 路由组
userGroup := router.Group("/user")
{
// 首页
userGroup.GET("/index", Index)
// 导入用户
userGroup.POST("/import", ImportUsers)
// 抽奖
userGroup.GET("/lucky", GetLuckyUser)
}
}
请求成功后,每个页面都是返回一个字符串(包含各自的信息)
func Index(c *gin.Context) {
c.String(http.StatusOK, "当前参与抽奖的用户人数:%d", len(userList))
}
func ImportUsers(c *gin.Context) {
strUsers := c.Query("users")
users := strings.Split(strUsers, ",")
// 在操作 全局变量 userList 之前加互斥锁,加完锁记得释放
mux.Lock()
defer mux.Unlock()
// 统计当前已经在参加抽奖的用户数量
currUserCount := len(userList)
// 将页面提交的用户导入到 userList 中,参与抽奖
for _, user := range users {
user = strings.TrimSpace(user)
if len(user) > 0 {
userList = append(userList, user)
}
}
// 统计当前总共参加抽奖人数
userTotal := len(userList)
c.String(http.StatusOK, "当前参与抽奖的用户数量:%d,导入的用户数量:%d", userTotal, (userTotal - currUserCount))
}
func GetLuckyUser(c *gin.Context) {
var user string
// 在操作 全局变量 userList 之前加互斥锁,加完锁记得释放
mux.Lock()
defer mux.Unlock()
count := len(userList)
if count > 1 {
seed := time.Now().UnixNano()
// 以随机数设置中奖用户, [0,count)中的随机值
lottery_index := rand.New(rand.NewSource(seed)).Int31n(int32(count))
user = userList[lottery_index]
// 当前参与抽奖用户减 1
userList = append(userList[0:lottery_index], userList[lottery_index+1:]...)
c.String(http.StatusOK, "中奖用户为:%s,剩余用户数:%d", user, count-1)
} else if count == 1 {
user = userList[0]
userList = userList[0:0] // 清空参与抽奖的用户列表
c.String(http.StatusOK, "中奖用户为:%s,剩余用户数:%d", user, count-1)
} else {
c.String(http.StatusOK, "当前无参与抽奖的用户,请导入新的用户。")
}
}
在httptestUtil.go
文件中主要封装了以下工具函数:
// ParseToStr 将map中的键值对输出成querystring形式
func ParseToStr(mp map[string]string) string {
values := ""
for key, val := range mp {
values += "&" + key + "=" + val
}
temp := values[1:]
values = "?" + temp
return values
}
func Get(uri string, router *gin.Engine) *httptest.ResponseRecorder {
// 构造get请求
req := httptest.NewRequest("GET", uri, nil)
// 初始化响应
w := httptest.NewRecorder()
// 调用相应的handler接口
router.ServeHTTP(w, req)
return w
}
构造POST请求,表单数据以 querystring
的形式加在uri之后
注意:form表单的参数可以通过 querystring
的形式附在URI地址后面进行传递
这种方式,POST 请求获取参数是时要调用 c.Query("users")
,而不是c.PostFprm("users")
,更不是c.Param("users)
当然直接使用 c.ShouldBind()
,让gin自动判断是哪种方式的请求参数。
代码如下:
// PostForm 根据特定请求uri和参数param,以表单形式传递参数,发起post请求返回响应
func PostForm(uri string, param map[string]string, router *gin.Engine) *httptest.ResponseRecorder {
req := httptest.NewRequest("POST", uri+ParseToStr(param), nil)
// 初始化响应
w := httptest.NewRecorder()
// 调用相应handler接口
router.ServeHTTP(w, req)
return w
}
// PostJson 根据特定请求uri和参数param,以Json形式传递参数,发起post请求返回响应
func PostJson(uri string, param map[string]interface{
}, router *gin.Engine) *httptest.ResponseRecorder {
// 将参数转化为json比特流
jsonByte, _ := json.Marshal(param)
// 构造post请求,json数据以请求body的形式传递
req := httptest.NewRequest("POST", uri, bytes.NewReader(jsonByte))
// 初始化响应
w := httptest.NewRecorder()
// 调用相应的handler接口
router.ServeHTTP(w, req)
return w
}
Golang规范是推荐一个方法写一个测试函数,并且以Test
开头,后面跟方法名。
为了测试代码是否并发安全,就将三个功能的测试都写在同一个测试函数里,于是就命名为了TestMVC
。
func TestMVC(t *testing.T) {
var w *httptest.ResponseRecorder
assert := assert.New(t)
// 1.测试 index 请求
urlIndex := "/user/index"
w = Get(urlIndex, router)
assert.Equal(200, w.Code)
assert.Equal("当前参与抽奖的用户人数:0", w.Body.String())
// 2.测试 import 请求,导入用户数
var wg sync.WaitGroup // 定义wg, 用来阻塞 goroutine
for i := 0; i < 100000; i++ {
// 开一个等待
wg.Add(1)
go func(i int) {
// i 不属于临界资源,是安全的
defer wg.Done() // 一个 goroutine 跑完后要减1,
// 测试 /user/import 请求,模拟从 form 表单中获取数据
param := make(map[string]string)
param["users"] = "user" + strconv.Itoa(i)
urlImport := "/user/import"
w = PostForm(urlImport, param, router)
assert.Equal(200, w.Code)
}(i)
}
// 等待上面的协程运行完,再接着测试
wg.Wait()
// 3.测试 urlIndex 请求,查看当前参与抽奖用户是否为 for 循环总数
w = Get(urlIndex, router)
assert.Equal(200, w.Code)
assert.Equal("当前参与抽奖的用户人数:100000", w.Body.String())
// 4.测试 抽奖
urlLucky := "/user/lucky"
w = Get(urlLucky, router)
assert.Equal(200, w.Code)
// 5.抽奖一次之后,再发起 index 请求,查看查看当前参与抽奖用户是否减 1
w = Get(urlIndex, router)
assert.Equal(200, w.Code)
assert.Equal("当前参与抽奖的用户人数:99999", w.Body.String())
}
运行结果如图:
如图:
在我个人电脑上,测试运行耗时:9.21s;根据users字段的名字也说明了执行了 100000次,因为是并发执行的,所以顺序肯定不是从1到100000按序显示的(谁抢到CPU资源谁执行)
从昨天晚上7点开始练习项目,进行单元测试,中间睡了6个小时吧。早上起来后,经过昨晚测试的磨练和学习,上午思路很清晰,不仅单元测试成功了,还将之前自己鼓捣的测试代码进行了重构和优化,直到今天上午11点多才正式完成。
第一次写Golang的httptest单元测试,整个过程就是边搜边学边实践,最后总算成功了。写一下 httptest 测试心得吧:
Testify
,能少写很多 if 判断,(主要用来判断响应码和响应实体)。刚开始我就是if else
写了很多判断,后来学了这个测试框架参考资料:
1.Gin官方测试文档
2.基于golang gin框架的单元测试
3.用 Testify 来改善 GO 测试和模拟
文章浏览阅读3.1k次,点赞3次,收藏15次。diff算法的作用计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面。传统diff算法通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展示1000个节点,得执行上亿次比较。。即便是CPU快能执行30亿条命令,也很难在一秒内计算出差异。React的diff算法(1)什么是调和?将..._安卓面试题 diff算法有哪些
文章浏览阅读2.6k次。在ubutun14 系统中安装vagrant执行vagrant up时会出现:==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 de_cmd中vagrant up超时
文章浏览阅读1w次,点赞8次,收藏40次。注:参考书籍《Python数据结构与算法》1.图的抽象数据类型定义Graph()新建一个空图;addVertex(vert)向图中添加一个顶点(vert)实例;addEdge(fromVert,toVert)向图中添加一条有向边,用于连接顶点fromVert,toVertaddEdge(fromVert,toVert,weight)向图中添加一条带权重(weight)的有向边getVertex(vertKey)在图中找到名为vertKey的顶点getVertices()以列表形_python 图算法
文章浏览阅读1.1w次,点赞5次,收藏80次。销售管理系统 JavaGUI Swing框架实现版_销售管理系统
文章浏览阅读372次。现在有两个集合set1 和 set2, 需要将这两个集合合并成一个集合。 思路很简单: 顺序查找set1中的元素set2是否包含,是,则找下一个,否,加入到set2。 最后返回set2scheme 实现:(define (union_set set1 set2) (if (null? set1) set2 (union_set (cdr set1) (a_set thinker
文章浏览阅读5k次。研究一下贝塞尔曲线./** * 贝塞尔方程 */ private class BeizerEvaluator implements TypeEvaluator { private PointF point1; private PointF point2; private PointF pointF;_android 贝塞尔曲线融合动画
文章浏览阅读1k次。不同类型的DMAGPIOPL general purpose AXIGP AXI utlilizing PS DMACHigh performance w/DMAACP w/DMA几种DMA的总结ZYNQ中不同应用类型的DMA 几个常用的 AXI 接口 IP 的功能(上面已经提到): AXI-DMA:实现从 PS 内存到 PL 高速传输高速通道 AXI-HP<---->AXI-Stream 的转换 AXI-FIFO-MM2S:实现从 PS 内存到._dma cdma
文章浏览阅读938次。声明:本文转载自http://www.chinacion.cn/article/7931.html,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。这一篇文章我们就用 Java 来生成一下仿金山词霸的海报。As long as you can still grab a breath, you fight. 只要一息尚存,就不得不战。有那么一段时..._java生成小程序二维码海报
文章浏览阅读870次。人工神经网络(ArtificialNeuralNetwork,即ANN)是从信息处理角度对人脑神经元网络进行抽象,是20世纪80年代以来人工智能领域兴起的研究热点,其本质是一种运算模型,由大量的节点(或称神经元)之间相互联接构成,在模式识别、智能机器人、自动控制、生物、医学、经济等领域已成功地解决了许多现代计算机难以解决的实际问题,表现出了良好的智能特性。_人工神经元的结构以及各部分功能
文章浏览阅读2.3k次。#windmill{ width:160px; height:160px; position:relative; -moz-transition:-moz-transform 2s ease-in-out; -webkit-transition:-webkit-transform 2s ease-in-out; -moz-transform:rotate(0deg); -webki_h5 css写风车转动
文章浏览阅读3.9k次。如何判断windows动态链接库是32还是64位从 http://www.cnblogs.com/conorpai/p/6393120.html 转载而来如果安装过Visual Studio的话,直接打开一个VS提供的控制台窗口,比如VS2012 x64 Native Tools Command Prompt.用下面的命令查看程序的头部信息:“dumpbin /_win32动态链接库有64位版本吗
文章浏览阅读2k次。原标题:从EMUI的6次里程碑事件,看华为对安卓生态的贡献从华为进入手机领域开始至今,EMUI的发展共经历了5次较大的里程碑事件。由于安卓的开源属性,因此,每一次关键问题的解决和核心难题的攻克都是对安卓生态发展的一次贡献。 EMUI5.0:天生一,一生快用安卓的机友都深有体会,安卓系统最大的顽疾就是用几个月,系统就会变得异常卡顿。针对该问题,华为手机在EMUI5.0的时候,推出了“天生一,一生快”..._华为对安卓的贡献