はじめに
go を実際に利用をしてきましたが復習も兼ねて、下記の内容を試しに動かしつつ確認をした内容をまとめました。自分用のメモとしてまとめているため、細かい解説はありません。
- func の定義方法の違いに関して
- context や goroutine の正しい扱い方に関して
- Ctrl+C, kill, Graceful Shutdown に関して
- 継承, 多態性, Embedding に関して
- testingパッケージ の使い方に関して
※ 何かリクエストがありましたら、コメントいただければ追記したいと思います
※ go の playground を利用しました。
「Share」を押下すると、記載したコードを表示できるリンクが生成されるので、便利ですね。
Struct の Func定義方法による違い
下記に移行しました。
context
下記に移行しました。
処理中断&処理停止(Kill)
下記に移行しました。
多態性
継承はないが、Embedding ならある。
濫用はご法度。同じ処理をコピペする必要があるのであれば、一考の余地あり。
但し、「Embedding」なので、同名に関して(json変換の利用など)は要注意。
type Command interface {
Execute()
Exit()
}
type CancellableCommand interface {
Command // interfaceの定義を内包できる(Execute() と Exit() も定義に含められている)
Cancel()
}
// CancellableCommand のfuncを定義
// = CancellableCommand interfaceとして扱える
type LuckeyCommand struct {
}
func (LuckeyCommand) Execute() {
// execute command
}
func (LuckeyCommand) Exit() {
// execute exit
}
func (LuckeyCommand) Cancel() {
// execute cancel
}
type DefaultCommand struct {
}
func (DefaultCommand) Execute() {
fmt.Println("execute default")
}
func (DefaultCommand) Exit() {
fmt.Println("exit")
}
type HappyCommand struct {
*DefaultCommand // DefaultCommand の内容を内包できる
}
func (HappyCommand) Exit() {
fmt.Println("happy")
}
func main() {
var luckeyCommand Command = &LuckeyCommand{} // Command型
if cmd, ok := luckeyCommand.(CancellableCommand); ok {
// cmd は、 CancellableCommand型 として扱える
}
switch cmd := luckeyCommand.(type) {
// どちらも当てはまるため、最初のcase に入る
case Command:
// cmd は Command型 として扱える
case CancellableCommand:
// cmd は CancellableCommand型 として扱える
}
happyCommand := &HappyCommand{
&DefaultCommand{},
} // HappyCommand型
happyCommand.Execute() // execute default
happyCommand.Exit() // happy
happyCommand.DefaultCommand.Exit() // exit
}
golang のテスト
動作検証目的
-
*testing.Tを利用 -
Testから始まる func名 -
$ go test ./...などで実行
処理時間計測目的
-
*testing.Bor*testing.PBを利用 -
Benchmarkから始まる func名 -
$ go test -bench . -benchmemなどで実行
標準出力の検証目的
-
testingのパッケージの利用は不要// Output: ${出力内容} -
Exampleから始まる func名 -
$ go test -v ./...などで実行
実装時の小技
BeforeAll , AfterAll
*testing.M-
TestMainの func名
func TestMain(m *testing.M) {
// BeforeAll 処理 はここに書く
testRunResultCode := m.Run()
// AfterAll 処理 はここに書く
os.Exit(testRunResultCode)
}
*testing.T と *testing.B の両方で利用する処理を共通化する
-
testing.TBを 引数にすれば、*testing.Tも*testing.Bも受け取れる
--short オプションを付けてテストをスキップ
if testing.Short() {
t.Skip("skip reason")
}
エラー発生時の行番号表示を呼び出し元として表示させる
-
t.Helper()を処理の最初に実行する- vscode で実行してNGだった場合、結果の表示時に、ファイルが見つからない扱いとなってしまう( 2024/01/09時点 )
Parameterized なテストを作る
import (
"testing"
"github.com/stretchr/testify/assert"
...
)
var (
target service.HogeInterface
)
func TestMain(m *testing.M) {
target = new(service.Hoge)
os.Exit(m.Run())
}
func TestSampleNormal(t *testing.T) {
type testCase struct {
title name
input string
expect: model.Fuga,
verify func(t *testing.T, log model.LogItem)
}
for _, test := range append([]testCase {
{
title: "TestSampleNormal_001_maxLength",
input: "123456",
expect: model.Fuga{},
verify: func(t *testing.T, log model.LogItem) {
// verify assert...
},
},
// 処理が必要な場合は、即時関数を利用 or before や after の func を testCase に定義して、実行するように定義
func() testCase {
inputVal := "987"
return testCase{
title: "TestSampleNormal_002_...",
input: inputVal,
expect: model.Fuga{},
verify: func(t *testing.T, log model.LogItem) {
// verify assert...
},
}
}(),
},
//検証する内容や処理が同じであれば、可読性が落ちない範囲でまとめる
func() []testCase {
tests := []testCase{}
// xx な場合
for _, xxTest := range []struct {
title string
input string
}{
} {
tests = append(tests, testCase{
title: "TestSampleNormal_003_" + xxTest.title,
input: xxTest.input,
verify: func(t *testing.T, log model.LogItem) {
// verify assert...
},
})
}
// zzz な場合...
// ...
return tests
}()...
) {
calledLogs := // logsMockSetup...
t.Run(test.title, func(t *testing.T) {
result := target.CreateFuga(test.input)
assert.Equal(t, result, test.expect)
test.verify(t, calledLogs)
})
}
}