当前位置:网站首页>TDD案例实战
TDD案例实战
2022-06-28 23:00:00 【Johns】
使用gin框架实现一个简单的手机号密码登录服务step1: 需求分析/任务拆分
step1: 需求分析/任务拆分
这个阶段至少要确认以下内容:
- 使用什么样的协议?
- 输入/输出参数有哪些?
- 其他细节
- 参数验证的规则
- 接口安全策略(签名规则)
需求分析后我们一般会做任务拆分/分解, 然后产出接口文档, 这个阶段一般需要前后端开发,产品,测试共同讨论:
step2: 编写接口测试用例
这个阶段我们主要是针对之前定义好的接口文档, 编写接口测试用例
step3. 编写单元测试用例
- 测试用例上的通用信息进行封装
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
)- 编写测试用例
// 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
// 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的话, 我们需要为这个方法独立设计测试用例:
// 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方法
// 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%覆盖了.
这并不说明我们的代码绝对没有问题, 只能说明我们的代码相对简洁
# 生成html报告
go test -coverprofile=c.out ./...
go tool cover -html=c.out -o coverage.htmlstep6. 一键执行所有的接口测试用例
- 需要注意浏览器时间和服务机器的时钟是否同步 http://127.0.0.1:3000/api/open/run_auto_test?id=18&token=e701e78e60a655867895a7d1e44f7afeeb94b3b2879d83ce8e4ed5c6589e8d4c&mode=html&email=false&download=false
image.png
接口测试报告:
边栏推荐
- 一文搞懂shell脚本
- 带链接跳转的微信红包封面制作教程和使用指南
- LeetCode 324 摆动排序 II[排序 双指针] HERODING的LeetCode之路
- 论文解读(DCN)《Towards K-means-friendly Spaces: Simultaneous Deep Learning and Clustering》
- How to use the apipost script - global variables
- Wave picking of WMS warehouse management system module
- Leetcode 324 swing sort ii[sort double pointer] the leetcode path of heroding
- 稳!上千微服务如何快速接入 Zadig(Helm Chart 篇)
- How powerful is the Zadig build? Practice together
- Huawei cloud gaussdb (for redis) unveiling issue 19: inventory of six second level capabilities
猜你喜欢

Powerful open source API interface visual management platform Yapi

On the necessity and solution of building a campus online teaching video convergence platform

Zadig + sonarqube, ensuring the safety of the development process

Tanghongbin, Yaya live CTO: to truly localize, the product should not have the attribute of "origin"

Detailed explanation of Zadig's self-test and joint debugging sub environment for developers

Panxiaoming, senior vice president of IC nansha|amd and President of Greater China: process, architecture and platform optimization break through the computing boundary

Encounter with avita 11: long lost freshness under strong product power

WMS仓库管理系统模块之波次拣货

如何结合均线分析伦敦金行情走势线图

Zadig officially launched vs code plug-in, making local development more efficient
随机推荐
Leetcode 324 Swing sort II [tri double pointeur] le chemin du leetcode pour l'héroding
Redis+aop+ user defined annotation to realize flow restriction
Qt5.15中qsrand,srand随机数生成函数已弃用问题
深入虚拟内存(Virtual Memory,VM)
The Best of Many Worlds_ Dual Mirror Descent for Online Allocation Problems
Online linear programming: Dual convergence, new algorithms, and regret bounds
Oracle deleting archived logs and adding scheduled tasks
Prometeus 2.36.0 new features
k线图基础知识图解——单根K线的含义
Common tool classes and Commons class libraries
Wave picking of WMS warehouse management system module
【HackTheBox】 meow
Is it safe and reliable to open a securities account in changtou school?
Master the usage of const
00 後雲原生工程師:用 Zadig 為思創科技(廣州公交)研發開源節流
Steady! How thousands of micro services can quickly access Zadig (helm chart)
长投学堂帮忙开证券账户是安全靠谱的吗?个人如何开
Online text filter less than specified length tool
CS5463代码模块解析(包含下载链接)
【HackTheBox】dancing(SMB)