Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >TDD案例实战

TDD案例实战

原创
作者头像
Johns
修改于 2022-06-30 02:20:33
修改于 2022-06-30 02:20:33
6960
举报
文章被收录于专栏:代码工具代码工具

使用gin框架实现一个简单的手机号密码登录服务step1: 需求分析/任务拆分 案例代码地址: https://github.com/GuoGuiRong/TDD-demo

step1: 需求分析/任务拆分

这个阶段至少要确认以下内容:

  • 使用什么样的协议?
  • 输入/输出参数有哪些?
  • 其他细节
    • 参数验证的规则
    • 接口安全策略(签名规则)

需求分析后我们一般会做任务拆分/分解, 然后产出接口文档, 这个阶段一般需要前后端开发,产品,测试共同讨论:

image.png
image.png

step2: 编写接口测试用例

这个阶段我们主要是针对之前定义好的接口文档, 编写接口测试用例

image.png
image.png

step3. 编写单元测试用例

  • 测试用例上的通用信息进行封装
代码语言:go
AI代码解释
复制
  var (
  	// 正常响应
  	Success = RetResp{0, "success"}
  
  	// 异常请求
  	BadRequest = RetResp{8000004000, "invalid request"}
  
  	// 登录请求参数解析异常
  	LoginParamsParseFailed = RetResp{8000004001, "request params parse failed"}
  
  	// 登录请求参数sign非法
  	LoginSignIllegal = RetResp{8000004002, "request sign illegal"}
  
  	// 登录请求过期
  	LoginTimestampExpire = RetResp{8000004003, "request timestamp expire"}
  
  	// 登录请求参数nonce非法
  	LoginNonceIllegal = RetResp{8000004004, "request nonce illegal"}
  
  	// 登录请求参数phone_number非法
  	LoginPhoneNumberIllegal = RetResp{8000004005, "request phone_number illegal"}
  
  	// 登录请求参数password非法
  	LoginPwdIllegal = RetResp{8000004006, "request password illegal"}
  
  	// 登录请求参数sign错误
  	LoginSignCheckFailed = RetResp{8000004007, "request sign check failed"}
  
  	// 账号phone_number不存在
  	LoginPhoneNumberNotExist = RetResp{8000004008, "request phone_number not exist"}
  
  	// 用户password错误
  	LoginPwdCheckFailed = RetResp{8000004009, "request password incorrect"}
  )
  
  // 通用常量
  var (
  	ExistPhoneNumber = "18018726093"
  	ExistPasswd      = "TesPwdT123"
  	Underline        = "_"
  	LoginPwdMinLen   = 6
  	LoginPwdNonceLen = 8
  	LoginPwdMaxLen   = 20
  	LoginSignLen     = 32
  )
  • 编写测试用例
代码语言:go
AI代码解释
复制
  // TestLoginPwdService A1: 登陆服务
  func TestLoginPwdService(t *testing.T) {
      tests := []struct {
        name     string
        args     Args
        wantCode int
      }{
        {"Correct Request Params", CorrectRequestParams, pkg.Success.Code},
        {"Bad Request", BadRequestParams, pkg.BadRequest.Code},
        {"Request Expire Time", RequestExpiredTime, pkg.LoginTimestampExpire.Code},
        {"Request PhoneNumber Invalid", RequestPhoneNumberInvalid, pkg.LoginPhoneNumberIllegal.Code},
        {"Request Password too short", RequestPwdTooShort, pkg.LoginPwdIllegal.Code},
        {"Request Password too long", RequestPwdTooLong, pkg.LoginPwdIllegal.Code},
        {"Request Invalid Password Content", RequestInvalidPwdContent, pkg.LoginPwdCheckFailed.Code},
        {"Request Sign Invalid: length not equals 32", RequestSignWithBadLength, pkg.LoginSignIllegal.Code},
        {"Request Invalid Sign Content", RequestSignWithBadContent, pkg.LoginSignCheckFailed.Code},
        {"Request Nonce Invalid", RequestNonceInvalid, pkg.LoginNonceIllegal.Code},
      }
  
      ast := assert.New(t)
      for _, tt := range tests {
        // 准备, mock一个gin.Context, 并把用例数据载入其中
        var ctx *gin.Context
        var w *responseWriter
        if tt.wantCode != pkg.BadRequest.Code {
          ctx, _, w = buildRequest(tt.args.PhoneNumber, tt.args.Password,
            tt.args.Timestamp, tt.args.Nonce, tt.args.Sign)
        } else {
          ctx, _, w = buildBadRequest(tt.args.Timestamp, tt.args.Nonce, tt.args.Sign)
        }
  			
        // 执行用例
        LoginPwdService(ctx)
        resp := LoginPwdResp{}
        err := json.Unmarshal([]byte(w.Buff.String()), &resp)
        
        // 断言
        ast.False(err != nil, tt.name)
        ast.Equal(resp.Code, tt.wantCode, tt.name)
      }
    }

step4. 实现LoginPwdService

代码语言:go
AI代码解释
复制
// LoginPwdService 密码登陆
func LoginPwdService(c *gin.Context) {

	// step1. 参数解析
	req, err := ParseLoginReqParams(c)
	if err != nil {
		// 退出
		c.JSON(http.StatusBadRequest, gin.H{
			"code":    pkg.BadRequest.Code,
			"message": err.Error(),
		})
		return
	}

	// step2. 参数验证
	code, err := CheckLoginReqParams(req.PhoneNumber, req.Password, req.Timestamp, req.Nonce, req.Sign)
	if err != nil {
		// 退出
		c.JSON(http.StatusBadRequest, gin.H{
			"code":    code,
			"message": err.Error(),
		})
		return
	}

	// step3: 签名验证
	code, err = CheckLoginSignature(req.PhoneNumber, req.Password, req.Timestamp, req.Nonce, req.Sign)
	if err != nil {
		// 退出
		c.JSON(http.StatusBadRequest, gin.H{
			"code":    code,
			"message": err.Error(),
		})
		return
	}

	// step4: 密码确认
	code, err = CheckLoginPwd(req.PhoneNumber, req.Password)
	if err != nil {
		// 退出
		c.JSON(http.StatusBadRequest, gin.H{
			"code":    code,
			"message": err.Error(),
		})
		return
	}

	// step5. 生成token
	token := pkg.MD5(req.Password+req.Timestamp, req.PhoneNumber)
	data := map[string]string{
		"token": token,
	}

	// step6. 响应结果验证
	c.JSON(http.StatusBadRequest, gin.H{
		"code":    pkg.Success.Code,
		"message": pkg.Success.Msg,
		"data":    data,
	})
	return
}

遇到子方法需要独立实现咋么办?

当我们编写实现时, 可能发现有些地方需要独立实现, 比如我们需要一个独立的CheckLoginSignature方法, 使用TDD的话, 我们需要为这个方法独立设计测试用例:

代码语言:go
AI代码解释
复制
// CheckSignature 签名检查
func CheckLoginSignature(phoneNumber, password, reqTimestamp, nonce, sign string) (code int, err error) {
  // 先不做具体实现, 只是定义输入输出
  // 初始化默认使用异常输出
  ....
  
	return pkg.LoginSignCheckFailed.Code, nil
}

// TestCheckSignature A0:测试签名
func TestCheckSignature(t *testing.T) {
	tests := []struct {
		name     string
		args     Args
		wantCode int
		wantErr  bool
	}{
		{"Correct Sign", CorrectRequestParams, pkg.Success.Code, false},
		{" Sign With Bad length ", RequestSignWithBadLength, pkg.LoginSignCheckFailed.Code, true},
		{" Sign Is Illegal ", RequestSignWithBadContent, pkg.LoginSignCheckFailed.Code, true},
	}

	ast := assert.New(t)
	for _, tt := range tests {
		got, err := CheckLoginSignature(tt.args.PhoneNumber, tt.args.Password, tt.args.Timestamp, tt.args.Nonce,
			tt.args.Sign)
		ast.Equal(got, tt.wantCode, tt.name)
		ast.Equal(err != nil, tt.wantErr, tt.name)
	}
}

然后我们为了让测试用例TestCheckSignature通过, 实现CheckLoginSignature方法

代码语言:go
AI代码解释
复制
// CheckSignature 签名检查
// MD5({phone_number}_{password}_{时间戳},{随机串})
func CheckLoginSignature(phoneNumber, password, reqTimestamp, nonce, sign string) (int, error) {
	sourceStr := phoneNumber + pkg.Underline + password + pkg.Underline + reqTimestamp
	signStr := pkg.MD5(sourceStr, nonce)
	fmt.Sprint("sign:" + signStr)
	if signStr != sign {
		return pkg.LoginSignCheckFailed.Code, errors.New(pkg.LoginSignCheckFailed.Msg)
	}
	return pkg.Success.Code, nil
}

当把内部子方法都使用TDD的方式实现后, 再实现LoginPwdService里面的具体调用, 最后执行TestLoginPwdService完成整个接口的单元测试.

step5. 执行所有单元测试

在项目目录下, 本地执行go test .../, 查看是否有没有覆盖到的地方, 我这里可以看到文件100%覆盖了.

这并不说明我们的代码绝对没有问题, 只能说明我们的代码相对简洁

image.png
image.png
代码语言:shell
AI代码解释
复制
# 生成html报告
go test -coverprofile=c.out  ./...
go tool cover -html=c.out -o coverage.html
image.png
image.png

step6. 一键执行所有的接口测试用例

接口测试报告:

image.png
image.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go 单元测试之HTTP请求与API测试
假设我们的业务逻辑是搭建一个http server端,对外提供HTTP服务。用来处理用户登录请求,用户需要输入邮箱,密码。
贾维斯Echo
2024/04/18
4970
Go 单元测试之HTTP请求与API测试
漏洞挖掘之从开发者视角解析Gin框架中的逻辑漏洞与越权问题
以go的gin后端框架为例子,详细剖析了各种逻辑越权漏洞的成因已经对应防范手段,也为白帽子提供挖掘思路
亿人安全
2025/04/03
2190
漏洞挖掘之从开发者视角解析Gin框架中的逻辑漏洞与越权问题
02 . Go框架之Gin框架从入门到熟悉(数据解析和绑定,渲染,重定向,同步异步,中间件)
json,结构体,xml, yaml类似于java的properties,protobuf
iginkgo18
2020/11/04
1.6K0
02 . Go框架之Gin框架从入门到熟悉(数据解析和绑定,渲染,重定向,同步异步,中间件)
go : gin 模型绑定与验证
将请求体绑定到类型中,请使用模型绑定。我们目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定
IT工作者
2022/07/23
3250
gin学习——邮箱发送验证码注册用户
qq邮箱-设置-账户 安装 go get -u github.com/jordan-wright/email go get -u github.com/garyburd/redigo/redis dao/user_dao.go func SendEmailValidate(em []string) (string,error) { e:=email.NewEmail() e.From = fmt.Sprintf("发件人笔名 <发件人邮箱>") e.To = em // 生成
传说之下的花儿
2023/04/16
1.2K0
gin学习——邮箱发送验证码注册用户
6.Gin 路由详解 - GET POST 请求以及参数获取示例
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的 Content-Type 识别请求数据类型并利用反射机制自动提取请求中 QueryString、form 表单、JSON、XML 等参数到结构体中。
Devops海洋的渔夫
2023/11/20
2.5K0
6.Gin 路由详解 - GET POST 请求以及参数获取示例
使用 Dify、Meilisearch、零一万物模型实现最简单的 RAG 应用(三):AI 电影推荐
这篇文章,我们继续聊聊,如何折腾 AI 应用,把不 AI 的东西,“AI 起来”。在不折腾复杂的检索系统的前提下,快速完成轻量的 RAG 实践。
soulteary
2024/05/20
1.1K0
初学gin
已经放弃goframe框架,对待新手不是很友好,社区圈子也很小。因为我自身的话是没有go语言的编程基础的,所以导致了我看不太懂那个框架,不过看很多人都说goframe封装的很好,有人吐槽有人夸,开源的框架嘛,这些都是在所难免的。
是小张啊喂
2021/08/09
8030
b站1024安全攻防挑战赛
getFormatter 这里就要返回 array(new Action(), 'run') 来让 call_user_fun_array 触发 run 函数,即 Content 里有 $this->formatters = array('close'=>array(new Action(), 'run'))
pankas
2022/11/11
9730
b站1024安全攻防挑战赛
使用 Dify、Meilisearch、零一万物模型实现最简单的 RAG 应用(三):AI 电影推荐
这篇文章,我们继续聊聊,如何折腾 AI 应用,把不 AI 的东西,“AI 起来”。在不折腾复杂的检索系统的前提下,快速完成轻量的 RAG 实践。
soulteary
2024/05/29
1.3K0
使用 Dify、Meilisearch、零一万物模型实现最简单的 RAG 应用(三):AI 电影推荐
gin——使用腾讯云存储图片
一、腾讯云对象存储COS 1. 创建储存桶 然后直接下一步-创建即可。 2. API密钥创建 3. 查看代码中需要的东西 储存桶名称Bucket、所属地域Region 密钥APPID、SecretId、SecretKey 二、代码 1. 配置文件conf.toml 这里读取配置文件的第三方库是Viper,学习可以查看:viper库快速使用 将上面查看的几个值写入配置文件对应位置即可 [tencentCOS] # TencentCloud 腾讯云
传说之下的花儿
2023/04/16
10K1
gin——使用腾讯云存储图片
boss: 这小子还不会使用validator库进行数据校验,开了~~~
哈喽,大家好,我是asong。这是我的第十篇原创文章。这周在公司做项目,在做API部分开发时,需要对请求参数的校验,防止用户的恶意请求。例如日期格式,用户年龄,性别等必须是正常的值,不能随意设置。最开始在做这一部分的时候,我采用老方法,自己编写参数检验方法,统一进行参数验证。后来在同事CR的时候,说GIN有更好的参数检验方法,gin框架使用github.com/go-playground/validator进行参数校验,我们只需要在定义结构体时使用binding或validatetag标识相关校验规则,就可以进行参数校验了,很方便。相信也有很多小伙伴不知道这个功能,今天就来介绍一下这部分。
Golang梦工厂
2022/07/07
7860
内部IOA鉴权登录
内网的项目虽然看起来非常安全外网无法访问, 但是也有可能遭遇黑客的攻击, 同时内网项目遭遇攻击后对于公司的损失是非常大的。 所以权限校验、身份验证、鉴权登录就非常重要了。
用户11097514
2024/05/23
3130
go语言中gin的用法
在这个示例中,我们使用了gin框架来创建一个简单的HTTP服务。我们定义了四个路由:
周辰晨
2024/03/18
2120
Go单测系列2—网络测试
这是Go语言单元测试从零到溜系列教程的第1篇,介绍了如何使用httptest和gock工具进行网络测试。
luckpunk
2023/09/10
5030
Go 和 Gin 打造一个带图库功能的随机图片 API?让我们一起走进 Go Web 开发的奇妙世界!
今日推荐:Spring AI再更新:如何借助全局参数实现智能数据库操作与个性化待办管理
繁依Fanyi
2024/11/15
1590
Gin-Web-Framework官方指南中文(下篇)
ShouldBind,ShouldBindJSON,ShouldBindXML,ShouldBindQuery,ShouldBindYAML
小诚信驿站
2019/10/31
2.4K0
Gin-Web-Framework官方指南中文(下篇)
Go Gin框架请求自动验证和数据绑定,看完这篇就会用了
之前做项目基本上公司是用 gRPC 和 echo 这两个框架的组合,后来 Gin 框架在Go圈越来越流行,陆续我在公司接触到的项目也开始有人用 Gin 框架开发了。
KevinYan
2023/01/03
3.5K0
Go Gin框架请求自动验证和数据绑定,看完这篇就会用了
Go: Gin框架中的Bind()方法技术解析
Gin框架的Bind()方法是Go开发者在Web开发中经常使用的一个功能,它支持自动地识别和转换多种数据类型。这一功能的实现显著提高了Web应用开发的效率和可维护性。本文将深入探讨Bind()方法背后的技术实现,解析它是如何处理不同数据类型的。
运维开发王义杰
2024/05/10
3940
Go: Gin框架中的Bind()方法技术解析
gin操作MySQL和Redis
go这些基础的东西,看起来很舒服,最起码对于写习惯java的人来讲,go真的很舒服,所以近一段时间可能一直连更,gin并没有直接封装操作MySQL的工具类,所以还是使用开源的工具
是小张啊喂
2021/08/10
2.9K0
推荐阅读
相关推荐
Go 单元测试之HTTP请求与API测试
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档