golangのコードを実行して理解する


# はじめに

go を実際に利用をしてきましたが復習も兼ねて、下記の内容を試しに動かしつつ確認をした内容をまとめました。自分用のメモとしてまとめているため、細かい解説はありません。

1. func の定義方法の違いに関して
1. context や goroutine の正しい扱い方に関して
1. Ctrl+C, kill, Graceful Shutdown に関して
1. 継承, 多態性, Embedding に関して
1. testingパッケージ の使い方に関して

※ 何かリクエストがありましたら、コメントいただければ追記したいと思います


※ [go の playground](https://go.dev/play/) を利用しました。
　「Share」を押下すると、記載したコードを表示できるリンクが生成されるので、便利ですね。


# Struct の Func定義方法による違い

下記に移行しました。

https://matsuyoido.f5.si/kanro/struct%e3%81%aefunc%e5%ae%9a%e7%be%a9/

# context

下記に移行しました。

https://matsuyoido.f5.si/kanro/context%e3%82%92%e4%bd%bf%e3%81%84%e3%81%93%e3%81%aa%e3%81%99/


# 処理中断&処理停止(Kill)

下記に移行しました。

https://matsuyoido.f5.si/kanro/main%e5%87%a6%e7%90%86%e3%82%92%e6%ad%a3%e5%b8%b8%e3%81%ab%e3%82%b3%e3%83%b3%e3%83%88%e3%83%ad%e3%83%bc%e3%83%ab%e3%81%99%e3%82%8b/


# 多態性

継承はないが、Embedding ならある。
濫用はご法度。同じ処理をコピペする必要があるのであれば、一考の余地あり。

但し、「Embedding」なので、同名に関して(json変換の利用など)は要注意。

https://go.dev/doc/effective_go#embedding

```golang
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名
```golang
func TestMain(m *testing.M) {
    // BeforeAll 処理 はここに書く
    testRunResultCode := m.Run()
    // AfterAll 処理 はここに書く
    os.Exit(testRunResultCode)
}
```


### `*testing.T` と `*testing.B` の両方で利用する処理を共通化する

* `testing.TB` を 引数にすれば、 `*testing.T` も `*testing.B` も受け取れる

### `--short` オプションを付けてテストをスキップ

```golang
if testing.Short() {
    t.Skip("skip reason")
}
```

### エラー発生時の行番号表示を呼び出し元として表示させる

* `t.Helper()` を処理の最初に実行する
    * vscode で実行してNGだった場合、結果の表示時に、ファイルが見つからない扱いとなってしまう( [2024/01/09時点](https://github.com/golang/vscode-go/issues/2444) )


### Parameterized なテストを作る

```golang
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)
        })
    }
}
```


# 参考

* https://zenn.dev/uji/articles/f6ab9a06320294146733
* https://zenn.dev/hsaki/books/golang-context/viewer/definition
* https://pkg.go.dev/testing