0
0

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

Last updated at Posted at 2024-01-16

はじめに

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

  1. func の定義方法の違いに関して
  2. context や goroutine の正しい扱い方に関して
  3. Ctrl+C, kill, Graceful Shutdown に関して
  4. 継承, 多態性, Embedding に関して
  5. 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)
        })
    }
}

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0