本文最后更新于 612 天前,其中的信息可能已经有所发展或是发生改变。
由 ChatGPT 生成的文章摘要
本文是“极简版抖音项目的实现”系列的第二篇文章,主要介绍了如何使用Mock和单元测试来保证代码的质量和稳定性。作者详细介绍了Mock的基本概念和如何使用Mock来模拟数据和接口,以及如何使用单元测试来测试代码的各个部分。通过Mock和单元测试的使用,可以大大提高代码的可维护性和健壮性。
极简版抖音项目的实现(2) —— Mock 和单元测试 | 青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第 15 天
完结撒花!小小庆祝一下。
前言
本文上接 极简版抖音项目的实现 | 青训营笔记,介绍了该项目视频流服务的单元测试代码。
进行单元测试
Go 原生支持单元测试(Unit Test),为了对我们的视频流服务进行单元测试,创建 handler_test.go
文件。
首先,在 TestMain
函数中初始化数据,这包括我们一些预先设定好的来自上游服务的测试数据和我们预期的结果数据:
const mockVideoCount = 50
var (
testVideos = make([]*model.Video, mockVideoCount)
respVideos = make([]*feed.Video, mockVideoCount)
)
var mockUser = user.User{Id: 65535}
func TestMain(m *testing.M) {
now := time.Now().UnixMilli()
for i := 0; i < mockVideoCount; i++ {
test := &model.Video{
Model: model.Model{
ID: uint32(i),
CreatedAt: time.UnixMilli(now).Add(time.Duration(i) * time.Second),
},
UserId: mockUser.Id,
Title: "Test Video " + strconv.Itoa(i),
FileName: "test_video_file_" + strconv.Itoa(i) + ".mp4",
CoverName: "test_video_cover_file_" + strconv.Itoa(i) + ".png",
}
resp := &feed.Video{
Id: uint32(i),
Author: &mockUser,
PlayUrl: "https://test.com/test_video_file_" + strconv.Itoa(i) + ".mp4",
CoverUrl: "https://test.com/test_video_cover_file_" + strconv.Itoa(i) + ".png",
FavoriteCount: 0, // TODO
CommentCount: 0, // TODO
IsFavorite: false, // TODO
Title: "Test Video " + strconv.Itoa(i),
}
testVideos[i] = test
respVideos[i] = resp
}
testVideos = reverseModelVideo(testVideos)
respVideos = reverseFeedVideo(respVideos)
code := m.Run()
os.Exit(code)
}
func reverseModelVideo(s []*model.Video) []*model.Video {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
func reverseFeedVideo(s []*feed.Video) []*feed.Video {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
上述代码创建了 50 个预设的视频数据,如果服务正常工作,那么应当返回 respVideos
值。
接下来,在 TestFeedServiceImpl_ListVideos
函数中编写正式的开发流程:
func TestFeedServiceImpl_ListVideos(t *testing.T) {
pTime := strconv.FormatInt(time.Now().Add(time.Duration(1)*time.Hour).UnixMilli(), 10)
var successArg = struct {
ctx context.Context
req *feed.ListFeedRequest
}{ctx: context.Background(), req: &feed.ListFeedRequest{
LatestTime: &pTime,
ActorId: nil,
}}
expectedNextTime := testVideos[biz.VideoCount-1].CreatedAt.Add(time.Duration(-1)).UnixMilli()
var successResp = &feed.ListFeedResponse{
StatusCode: biz.OkStatusCode,
StatusMsg: &biz.OkStatusMsg,
NextTime: &expectedNextTime,
Videos: respVideos[:biz.VideoCount],
}
UserClient = MockUserClient{}
monkey.Patch(storage.GetLink, func(fileName string) (string, error) {
return "https://test.com/" + fileName, nil
})
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "deleted_at", "user_id", "title", "file_name", "cover_name"})
for _, v := range testVideos[:biz.VideoCount] {
rows.AddRow(v.ID, v.CreatedAt, nil, nil, v.UserId, v.Title, v.FileName, v.CoverName)
}
mock.DBMock.
ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "videos" WHERE "videos"."created_at" <= $1 AND "videos"."deleted_at" IS NULL ORDER BY "videos"."created_at" DESC LIMIT ` + strconv.Itoa(biz.VideoCount))).
WillReturnRows(rows)
type args struct {
ctx context.Context
req *feed.ListFeedRequest
}
tests := []struct {
name string
args args
wantResp *feed.ListFeedResponse
wantErr bool
}{
{name: "Feed Video", args: successArg, wantResp: successResp},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &FeedServiceImpl{}
gotResp, err := s.ListVideos(tt.args.ctx, tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("ListVideos() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(gotResp.Videos) != len(tt.wantResp.Videos) {
t.Errorf("ListVideos() lens got %v, want %v", len(gotResp.Videos), len(tt.wantResp.Videos))
}
if len(gotResp.Videos) != biz.VideoCount {
t.Errorf("ListVideos() lens got %v, want %v", len(gotResp.Videos), biz.VideoCount)
}
if !reflect.DeepEqual(gotResp, tt.wantResp) {
t.Errorf("ListVideos() gotResp %v, want %v", gotResp, tt.wantResp)
}
})
}
}
type MockUserClient struct {
}
func (m MockUserClient) GetUser(ctx context.Context, Req *user.UserRequest, callOptions ...callopt.Option) (r *user.UserResponse, err error) {
return &user.UserResponse{StatusCode: biz.OkStatusCode, User: &mockUser}, nil
}
上述代码首先初始化了一些请求数据,然后对 UserClient
和数据库进行了 Mock(Mock 可以理解为,将原有的函数和方法实现替换成仅在测试可用的特殊实现),以期其返回我们的期望数据,最后,对这些数据进行比对。
引用
该文章部分内容来自于以下课程或网页:
分发
This work is licensed under CC BY-SA 4.0