🧪

【Go】Goのテストに入門してみた! ~テーブル駆動ベンチマークテスト編~

に公開

はじめに

前回は「ベンチマーク」を見ていきました。
今回は「テーブル駆動ベンチマークテスト」に入門します。

前回の記事はこちら!
https://zenn.dev/tmyhrn/articles/fd7e9de821e631

この記事でわかること

  • テーブル駆動テストをベンチマークテストで活用する方法
  • テーブル駆動ベンチマークテストのメリットと不要なケース

テーブル駆動ベンチマークテスト

前回の記事で、文字列結合についてのベンチマークをとりました。
今回はそれをテーブル駆動テストで実行してみます。

main_test.go
package main

import (
	"fmt"
	"strings"
	"testing"
)

const N = 100000

func generateStrings() []string {
    s := make([]string, N)
    for i := 0; i < N; i++ {
        s[i] = "Hello"
    }
    return s
}

func BenchmarkStringConcatTable(b *testing.B) {
    s := generateStrings()
    
    benchmarks := []struct {
        name string
        fn   func([]string)
    }{
        {
            name: "Plus",
            fn: func(s []string) {
                result := ""
                for _, v := range s {
                    result += v + " "
                }
                _ = result
            },
        },
        {
            name: "Sprintf",
            fn: func(s []string) {
                result := ""
                for _, v := range s {
                    result = fmt.Sprintf("%s %s", result, v)
                }
                _ = result
            },
        },
        {
            name: "Join",
            fn: func(s []string) {
                _ = strings.Join(s, " ")
            },
        },
        {
            name: "Builder",
            fn: func(s []string) {
                var sb strings.Builder
                for _, v := range s {
                    sb.WriteString(v)
                    sb.WriteString(" ")
                }
                _ = sb.String()
            },
        },
    }
    
    for _, bm := range benchmarks {
        b.Run(bm.name, func(b *testing.B) {
            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                bm.fn(s)
            }
        })
    }
}

テストを実行すると、以下のような結果を得ることができます。

ターミナル
# go test -benchmem -bench .

goos: darwin
goarch: arm64
pkg: go-test-practice
cpu: Apple M1
BenchmarkStringConcatTable
BenchmarkStringConcatTable/Plus
BenchmarkStringConcatTable/Plus-8                      1        5262879083 ns/op        30393265200 B/op          100139 allocs/op
BenchmarkStringConcatTable/Sprintf
BenchmarkStringConcatTable/Sprintf-8                   1        5221436792 ns/op        60437814320 B/op          398183 allocs/op
BenchmarkStringConcatTable/Join
BenchmarkStringConcatTable/Join-8                   1615            721619 ns/op          606209 B/op          1 allocs/op
BenchmarkStringConcatTable/Builder
BenchmarkStringConcatTable/Builder-8                1312            865771 ns/op         3242753 B/op         31 allocs/op
PASS
ok      go-test-practice        14.320s

テーブル駆動テストでベンチマークをとるメリット

  • ベンチマーク対象を増やしても、テーブルにエントリを追加するだけで済む
  • 共通の準備(generateStrings)を1回だけで済ませて効率的
  • コードの重複を避け、保守性が上がる

ただし、単純な関数の実行時間だけを測りたいときや、テスト条件が増えない場合などは、テーブル駆動テスト形式でベンチマークをとる必要はなさそうです。

まとめ

今回は「テーブル駆動ベンチマークテスト」について学びました。

テーブル形式にすることで、関数ごとのベンチマーク結果を見やすく整理でき、関数の追加も簡単です。
また、共通処理をまとめることで、コードの再利用性や保守性が向上します。
ただし、テスト対象が1つしかないなど、比較が不要な場合には通常のベンチマークのほうがシンプルで適しています。

次回は「Fuzzingテスト」に入門します!

参考

https://pkg.go.dev/testing#hdr-Benchmarks
https://blog.cloudsmith.co.jp/2024/03/444/
https://zenn.dev/canary_techblog/articles/2c11335be49f84

Discussion