はじめに
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.B
or*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)
})
}
}