前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >极简版抖音项目的实现(2) —— Mock 和单元测试 | 青训营笔记

极简版抖音项目的实现(2) —— Mock 和单元测试 | 青训营笔记

作者头像
HikariLan贺兰星辰
发布2023-03-06 18:49:51
发布2023-03-06 18:49:51
62500
代码可运行
举报
文章被收录于专栏:HikariLan's BlogHikariLan's Blog
运行总次数:0
代码可运行

极简版抖音项目的实现(2) —— Mock 和单元测试 | 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 15 天

完结撒花!小小庆祝一下。

前言

本文上接 极简版抖音项目的实现 | 青训营笔记,介绍了该项目视频流服务的单元测试代码。

进行单元测试

Go 原生支持单元测试(Unit Test),为了对我们的视频流服务进行单元测试,创建 handler_test.go 文件。

首先,在 TestMain 函数中初始化数据,这包括我们一些预先设定好的来自上游服务的测试数据和我们预期的结果数据:

代码语言:javascript
代码运行次数:0
复制
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 函数中编写正式的开发流程:

代码语言:javascript
代码运行次数:0
复制
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 可以理解为,将原有的函数和方法实现替换成仅在测试可用的特殊实现),以期其返回我们的期望数据,最后,对这些数据进行比对。

引用

该文章部分内容来自于以下课程或网页:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-2-12 2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 极简版抖音项目的实现(2) —— Mock 和单元测试 | 青训营笔记
    • 前言
    • 进行单元测试
    • 引用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档