3

在 Go 語言測試使用 Setup 及 Teardown

 2 years ago
source link: https://blog.wu-boy.com/2022/07/setup-and-teardown-with-unit-testing-in-golang/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
logo

相信大家在寫測試時,都會需要啟動而外服務,像是 RedisPostgres 等,而開始測試前會需要初始化資料庫連線,或者是準備測試資料,測試結束後就關閉資料庫連線,並且移除不必要的測試資料或檔案。在 Go 語言內開發者不用去依賴第三方的套件,透過內建的 TestMain 就可以非常輕鬆完成此事情。底下看看如何操作及使用。

整合 TestMain

Go 語言在測試套件內直接提供了 TestMain 函式,功能就是讓開發者可以在開始測試前準備環境 (setup) 或是測試結束後移除環境 (teardown)。底下看看正常執行範例

func TestMain(m *testing.M) {
  // call flag.Parse() here if TestMain uses flags
  os.Exit(m.Run())
}

接著可以新增 setup()teardown() 函式

func setup() {
  // Do something here.
  fmt.Printf("\033[1;33m%s\033[0m", "> Setup completed\n")
}

func teardown() {
  // Do something here.
  fmt.Printf("\033[1;33m%s\033[0m", "> Teardown completed")
  fmt.Printf("\n")
}

func TestMain(m *testing.M) {
  setup()
  code := m.Run()
  teardown()
  os.Exit(code)
}

最後執行 go test -v . 後可以看到底下結果

> Setup completed
testing: warning: no tests to run
PASS
> Teardown completed
ok      test    0.299s

這是符合我們的需求,可以在任何測試前準備環境,結束後可以移除相關環境,底下就是初始化 Groutine Pool,結束後釋放連線。

func setup() {
  // initial worker
  queue.New(cfg.Worker.NumProcs, cfg.Worker.MaxQueue).Run()
  fmt.Printf("\033[1;33m%s\033[0m", "> Setup completed\n")
}

func teardown() {
  // initial worker
  queue.Realse()
  fmt.Printf("\033[1;33m%s\033[0m", "> Teardown completed")
  fmt.Printf("\n")
}

單獨測試使用 Setup 及 Teardown

除了在整體測試前及測試後需要使用外,開發者也可能有需求在測試子項目 (sub-testing) 上。直接看底下範例,測試使用者是否存在

func TestIsUserExist(t *testing.T) {
  assert.NoError(t, PrepareTestDatabase())
  type args struct {
    uid   int64
    email string
  }
  tests := []struct {
    name    string
    args    args
    want    bool
    wantErr bool
  }{
    {
      name:    "test email exist without login",
      args:    args{0, "[email protected]"},
      want:    true,
      wantErr: false,
    },
    {
      name:    "test email not exist without login",
      args:    args{0, "[email protected]"},
      want:    false,
      wantErr: false,
    },
    {
      name:    "test email exist with login",
      args:    args{1, "[email protected]"},
      want:    true,
      wantErr: false,
    },
    {
      name:    "test email not exist with login",
      args:    args{1, "[email protected]"},
      want:    false,
      wantErr: false,
    },
  }
  for _, tt := range tests {
    tt := tt
    t.Run(tt.name, func(t *testing.T) {
      got, err := IsUserExist(tt.args.uid, tt.args.email)
      if (err != nil) != tt.wantErr {
        t.Errorf("isUserExist() error = %v, wantErr %v", err, tt.wantErr)
        return
      }
      if got != tt.want {
        t.Errorf("isUserExist() = %v, want %v", got, tt.want)
      }
    })
  }
}

接著新增 setupTest 函式

func setupTest(tb testing.TB) func(tb testing.TB) {
  fmt.Printf("\033[1;33m%s\033[0m", "> Setup Test\n")

  return func(tb testing.TB) {
    fmt.Printf("\033[1;33m%s\033[0m", "> Teardown Test\n")
  }
}

最後修改 t.Run 內容

t.Run(tt.name, func(t *testing.T) {
  teardownTest := setupTest(t)
  defer teardownTest(t)
  got, err := IsUserExist(tt.args.uid, tt.args.email)
  if (err != nil) != tt.wantErr {
    t.Errorf("isUserExist() error = %v, wantErr %v", err, tt.wantErr)
    return
  }
  if got != tt.want {
    t.Errorf("isUserExist() = %v, want %v", got, tt.want)
  }
})

整合 TestMain + Teardown

我們來將上述的案例整合一起使用,先寫一個簡單的 ToString 功能

package foobar

import "fmt"

// ToString convert any type to string
func ToString(value interface{}) string {
  if v, ok := value.(*string); ok {
    return *v
  }
  return fmt.Sprintf("%v", value)
}

接著寫測試

package foobar

import (
  "fmt"
  "os"
  "reflect"
  "testing"
)

func setup() {
  // Do something here.
  fmt.Printf("\033[1;33m%s\033[0m", "> Setup completed\n")
}

func teardown() {
  // Do something here.
  fmt.Printf("\033[1;33m%s\033[0m", "> Teardown completed")
  fmt.Printf("\n")
}

func TestMain(m *testing.M) {
  setup()
  code := m.Run()
  teardown()
  os.Exit(code)
}

func setupTest(tb testing.TB) func(tb testing.TB) {
  fmt.Printf("\033[1;34m%s\033[0m", ">> Setup Test\n")

  return func(tb testing.TB) {
    fmt.Printf("\033[1;34m%s\033[0m", ">> Teardown Test\n")
  }
}

func TestToString(t *testing.T) {
  type args struct {
    value interface{}
  }
  tests := []struct {
    name string
    args args
    want interface{}
  }{
    {
      name: "int",
      args: args{
        value: 101,
      },
      want: "101",
    },
    {
      name: "int64",
      args: args{
        value: int64(100),
      },
      want: "100",
    },
    {
      name: "boolean",
      args: args{
        value: true,
      },
      want: "true",
    },
    {
      name: "float32",
      args: args{
        value: float32(23.03),
      },
      want: "23.03",
    },
  }
  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      teardown := setupTest(t)
      defer teardown(t)
      if got := ToString(tt.args.value); !reflect.DeepEqual(got, tt.want) {
        t.Errorf("ToString() = %v, want %v", got, tt.want)
      }
    })
  }
}

最後結果如下

$ go test -v .
> Setup completed
=== RUN   TestToString
=== RUN   TestToString/int
>> Setup Test
>> Teardown Test
=== RUN   TestToString/int64
>> Setup Test
>> Teardown Test
=== RUN   TestToString/boolean
>> Setup Test
>> Teardown Test
=== RUN   TestToString/float32
>> Setup Test
>> Teardown Test
--- PASS: TestToString (0.00s)
    --- PASS: TestToString/int (0.00s)
    --- PASS: TestToString/int64 (0.00s)
    --- PASS: TestToString/boolean (0.00s)
    --- PASS: TestToString/float32 (0.00s)
PASS
> Teardown completed
ok      test    0.293s
Testing

See also


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK