前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >造轮子:自动生成带mock的table driving test

造轮子:自动生成带mock的table driving test

作者头像
golangLeetcode
发布2022-08-02 19:13:23
发布2022-08-02 19:13:23
48600
代码可运行
举报
运行总次数:0
代码可运行

我们一般会用gotests 来生成golang测试代码,但是这个工具并不是那么好用,遇到了下面这些问题:

1,gomock 支持不友好,没有统一的模板,导致测试代码格式不统一,阅读性差

比如:

1.1 gomock 初学者喜欢写全局的mock代码,特别是引入了,anyTime()以后,会导致sutests 之间相互影响,提升了问题排查难度。

1.2 一些参数,没有统一的管理传值,规范,比如context,很可能会导致mock调用之间值,传错

2,生成的代码golangci-lint 不通过

代码语言:javascript
代码运行次数:0
复制
golangci-lint run . party_test.go:24:20: Using the variable on range scope tt in function literal (scopelint) visitorLister: tt.fields.visitorLister, ^ party_test.go:25:20: Using the variable on range scope tt in function literal (scopelint) greeter: tt.fields.greeter, ^ party_test.go:27:30: Using the variable on range scope tt in function literal (scopelint) if err := s.GreetVisitors(tt.args.justNice); (err != nil) != tt.wantErr { ^ party_test.go:28:74: Using the variable on range scope tt in function literal (scopelint) t.Errorf("PartyService.GreetVisitors() error = %v, wantErr %v", err, tt.wantErr)

3,需要在case段和 driving 段同时添加代码,写case 来回滚动屏幕,不方便,特别是case很多的时候。

代码语言:javascript
代码运行次数:0
复制
package main

import (
  "fmt"
  "testing"

  "party/greet"
  mock_greet "party/greet/mock"

  "github.com/golang/mock/gomock"
)

func TestPartyService_GreetVisitors(t *testing.T) {
  type fields struct {
    visitorLister greet.VisitorLister
    greeter       greet.Greeter
  }
  type args struct {
    justNice bool
  }
  ctrl := gomock.NewController(t)
  defer ctrl.Finish()
  mockedVisitorLister := mock_greet.NewMockVisitorLister(ctrl)

  tests := []struct {
    name    string
    fields  fields
    args    args
    wantErr bool
  }{
    // TODO: Add test cases.
    {
      name: "ccase1",
      fields: fields{
        visitorLister: mockedVisitorLister,
      },
      wantErr: true,
    },
  }
  for _, tt := range tests {
    mockedVisitorLister.EXPECT().ListVisitors(greet.NiceVisitor).Return([]greet.Visitor{{"Peter", "TheSmart"}}, nil)
    t.Run(tt.name, func(t *testing.T) {
      s := &PartyService{
        visitorLister: tt.fields.visitorLister,
        greeter:       tt.fields.greeter,
      }
      if err := s.GreetVisitors(tt.args.justNice); (err != nil) != tt.wantErr {
        t.Errorf("PartyService.GreetVisitors() error = %v, wantErr %v", err, tt.wantErr)
      }
    })
  }
}

4,gomock 函数入参和返回值ide提示不友好,导致,写mock时候找这些类型,困难。

为了解决上述问题,提升写test的效率,开发了一个支持mock的table driving test 工具。

源码地址:https://github.com/xiazemin/autotest

安装:

代码语言:javascript
代码运行次数:0
复制
go get -u github.com/xiazemin/autotest

入门example

代码语言:javascript
代码运行次数:0
复制
https://github.com/xiazemin/tabel_drving_test_learn

使用介绍

下载后,在vscode 中,鼠标,右键,generate unite test for function 就可以生成,带gomock的和test case 信息的代码。

主要原理介绍,针对上面的测试代码可以做下调整,把

代码语言:javascript
代码运行次数:0
复制
  ctrl := gomock.NewController(t)
  defer ctrl.Finish()

放到case运行式的循环中,通过作用域的限制,消除case间的影响。

然后通过闭包的方式,把mock代码写到case里,避免了代码的来回切换

代码语言:javascript
代码运行次数:0
复制
tests := []struct {
    name    string
    fields  fields
    prepare func(fields *fields, args *args)
    args    args
    wantErr bool
  }
代码语言:javascript
代码运行次数:0
复制
    if tt.prepare != nil {
      tt.prepare(&tt.fields, &tt.args)
    }

通过上面的调整,我们可以专注的写case,但是,上述代码,很多都是重复劳动,所以可以考虑自动生成。自动生成的原理,大致经过了,编译,链接,渲染几个过程,生成的最终代码如下

代码语言:javascript
代码运行次数:0
复制
package main

import (
  "party/greet"
  "testing"

  mock_greet "party/greet/mock"

  "github.com/golang/mock/gomock"
)

func TestPartyService_GreetVisitors(t *testing.T) {
  type fields struct {
    visitorLister *mock_greet.MockVisitorLister

    greeter *mock_greet.MockGreeter
  }
  type args struct {
    justNice bool
  }
  tests := []struct {
    name    string
    fields  fields
    prepare func(fields *fields, args *args)
    args    args
    wantErr bool
  }{
    // TODO: Add test cases.
    {
      name: "case1",
      prepare: func(fields *fields, args *args) {
        gomock.InOrder(

          fields.visitorLister.EXPECT().ListVisitors(greet.VisitorGroup{}).Return([]greet.Visitor{},
            error,
          ).Times(2),

          fields.visitorLister.EXPECT().ListVisitors2(&greet.VisitorGroup{}).Return([]*greet.Visitor{},
            error,
          ).Times(2),

          fields.greeter.EXPECT().Hello(string).Return(string).Times(2),

          fields.greeter.EXPECT().Hello1(string,
            string,
          ).Return(string,
            string,
          ).Times(2),
        )
      },
    },
  }
  for _, tt := range tests {
    tt := tt
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    tt.fields.visitorLister = mock_greet.NewMockVisitorLister(ctrl)
    tt.fields.greeter = mock_greet.NewMockGreeter(ctrl)
    if tt.prepare != nil {
      tt.prepare(&tt.fields, &tt.args)
    }

    s := &PartyService{
      visitorLister: tt.fields.visitorLister,
      greeter:       tt.fields.greeter,
    }
    if err := s.GreetVisitors(tt.args.justNice); (err != nil) != tt.wantErr {
      t.Errorf("%q. PartyService.GreetVisitors() error = %v, wantErr %v", tt.name, err, tt.wantErr)
    }
  }
}

研发可以专注写case段代码,仅仅需要调整下面这一部分,不用观其他无关逻辑。

代码语言:javascript
代码运行次数:0
复制
 // TODO: Add test cases.
    {
      name: "case1",
      prepare: func(fields *fields, args *args) {
        gomock.InOrder(

          fields.visitorLister.EXPECT().ListVisitors(greet.VisitorGroup{}).Return([]greet.Visitor{},
            error,
          ).Times(2),

          fields.visitorLister.EXPECT().ListVisitors2(&greet.VisitorGroup{}).Return([]*greet.Visitor{},
            error,
          ).Times(2),

          fields.greeter.EXPECT().Hello(string).Return(string).Times(2),

          fields.greeter.EXPECT().Hello1(string,
            string,
          ).Return(string,
            string,
          ).Times(2),
        )
      },
    },
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档