miyazi888の覚え書き日記

学習したことを書き留めてます。

claude codeのVS Code拡張がインストールされない問題への対応

2025/6/24追記

下記の記事で知ったのですが、どうやら今はVSCode上から検索可能になった、とのことでした。 https://dev.classmethod.jp/articles/claude-code-vscode-extension-review/

概要

とうとうclaude codeをセットアップして、本格的にAIコーディングを試してみようとした。 公式サイトや初心者向けのyoutube動画などを見ながらいろいろと試しはじめたが、VSCode拡張のインストールに手こずってしまったので、その時の記録。

症状

VS-Code上で/ideコマンドを起動しても対象となるIDEが見つからない、というメッセージが表示され、その以上は何もできない状態だった。

対応方法

結論はこちらの記事のとおり、claude codeのnpmインストールディレクトリ上のvenderディレクトリにvscode拡張機能ファイルが置いてあるのでこのディレクトリまで移動して手動で格納機能をインストールすることで対応できた。

❯ code --install-extension claude-code.vsix
Installing extensions...
Extension 'claude-code.vsix' was successfully installed.

しかし、最初、そもそもclaude-codeのnpmがどこにインストールされているかがわからない、という問題に直面した。 主要なプログラム言語などをasdfで管理していたので~/.asdf以下だろうとは考えて、適当なディレクトリを探ってみたが、見つからない。

仕方がないのでclaudeに質問したら、すぐにわかった。

> npxコマンドをグローバルインストールしましたが、インストール先がわかりません。インストール先を調べる方法を教えてください。

● Bash(npm list -g --depth=0)
  ⎿  ~/.asdf/installs/nodejs/23.11.1/.npm/lib
     └── @anthropic-ai/claude-code@1.0.24

● Bash(npm root -g)
  ⎿  ~/.asdf/installs/nodejs/23.11.1/.npm/lib/node_modules

● グローバルインストール先は ~/.asdf/installs/nodejs/23.11.1/.npm/lib/node_modules です。
  調べる方法:
  - npm root -g: グローバルインストールディレクトリを表示
  - npm list -g --depth=0: グローバルにインストールされたパッケージ一覧を表示

もちろん、上の回答にあるとおり、npm root -gでインストール先を知ることができる。

❯ npm root -g
~/.asdf/installs/nodejs/23.11.1/.npm/lib/node_modules

インストール先がわかったのであとはVS拡張機能のインストールのみ。

cd ~/.asdf/installs/nodejs/23.11.1/.npm/lib/node_modules
cd @anthropic-ai/claude-code/vendor
code --install-extension claude-code.vsix

そして再度、VSCodeを再度起動してVSCode内のターミナルでideコマンドを実行。

今度は無事に連携できた。 良かった。

go-dynoについて調べてみた

きっかけ

技術書典で購入した、「SGE Go Tech Book Vol.07」という下記の本でgo-dynoの存在を知り、面白そうだったので軽く調べてみた。

techbookfest.org

概要

go-dynoは動的にメソッド呼び出しをproxyしてくれるライブラリ。どうやらJavaの動的proxy機構をgoで実現したくて作った模様。 横断的な処理を任意のinterfaceやstructに対して追加し、機能追加することが可能になる。

dynoが提供するproxyにより、Handleメソッド内で定義された内容を実行して、任意のタイミングでオリジナルのメソッドが実行されるようにできる。 イメージとしてはserverのmiddlewareと同じようなことを任意のinterfaceやstructに対して実施できるようになる。

こちらのツイートを見るとどうやらモックライブラリのmockioと同じ作者でmockio内部で使用されている、とのこと。

使い方

公式のサンプルとほぼ同じだが、少しコメントなどをつけ加えている。

今回の場合であれば、Greet1とGreet2の2つのメソッドが実行される前後で、それぞれ”Before〜"と"After〜"が表示されている。

main.go

package main

import (
    "fmt"
    "reflect"

    "github.com/ovechkin-dm/go-dyno/pkg/dyno"
)

// target interface
type Greeter interface {
    Greet1()
    Greet2()
}

// target
type SimpleGreeter struct{}

func (g *SimpleGreeter) Greet1() {
    fmt.Println("Hello1!")
}

func (g *SimpleGreeter) Greet2() {
    fmt.Println("Hello2!")
}

// proxy
type ProxyHandler[T any] struct {
    Impl T
}

func (p *ProxyHandler[T]) Handle(m reflect.Method, values []reflect.Value) []reflect.Value {
    fmt.Println("Before process: method call:", m.Name)
    ret := reflect.ValueOf(p.Impl).MethodByName(m.Name).Call(values)
    fmt.Println("After process: method called:", m.Name)
    return ret
}

func NewDynamicGreeter() Greeter {
    greeter := &SimpleGreeter{}
    proxyHandler := &ProxyHandler[Greeter]{Impl: greeter}
    dynamicGreeter, _ := dyno.Dynamic[Greeter](proxyHandler.Handle)
    return dynamicGreeter
}

func main() {
    dynamicGreeter := NewDynamicGreeter()
    dynamicGreeter.Greet1()
    dynamicGreeter.Greet2()
}

// 結果
// Before process: method call: Greet1
// Hello1!
// After process: method called: Greet1
// Before process: method call: Greet2
// Hello2!
// After process: method called: Greet2

これを使うことで横断的に処理したい内容(例えばロギングとかOpenTelemetolyのスパンをとか)を元の処理には手を加えず、コンストラクタに手を加えるだけで差し挟むことができるようになる。

ただ、メソッド呼び出しだけとはいえ、リフレクションでの呼び出しになるのでその分、パフォーマンスは犠牲になる。 犠牲になるのはどのぐらいか? 調べてみた。

ベンチマーク

最初のプログラムをさらに改造してベンチマークに適した形にし、ベンチマーク用のコードを追加し、性能を検証してみた。

修正内容は以下のとおり。 * ベンチマークには邪魔になるのでfmt.Printlnでの表示を削除し、Greet関数は文字列を返却するだけに修正 * NewDynamicGreeter関数ではgo-dynoのproxy経由でのインスタンスを提供 * NewStaticGreeter関数ではgo-dynoを経由しないインスタンスを提供 * ベンチマーク用のbenchmark_test.goを追加

main.go

package main

import (
    "reflect"

    "github.com/ovechkin-dm/go-dyno/pkg/dyno"
)

// target interface
type Greeter interface {
    Greet() string
}

// target
type SimpleGreeter struct{}

func (g *SimpleGreeter) Greet() string {
    return "Hello!"
}

// proxy
type ProxyHandler[T any] struct {
    Impl T
}

func (p *ProxyHandler[T]) Handle(m reflect.Method, values []reflect.Value) []reflect.Value {
    return reflect.ValueOf(p.Impl).MethodByName(m.Name).Call(values)
}

func NewDynamicGreeter() Greeter {
    greeter := &SimpleGreeter{}
    proxyHandler := &ProxyHandler[Greeter]{Impl: greeter}
    dynamicGreeter, _ := dyno.Dynamic[Greeter](proxyHandler.Handle)
    return dynamicGreeter
}

func NewStaticGreeter() Greeter {
    return &SimpleGreeter{}
}

func main() {
    dynamicGreeter := NewDynamicGreeter()
    dynamicGreeter.Greet()
}

benchmark_test.go

package main

import "testing"

func BenchmarkToDynamicGreeter(b *testing.B) {
    greeter := NewDynamicGreeter()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        greeter.Greet()
    }
}

func BenchmarkToStaticGreeter(b *testing.B) {
    greeter := NewStaticGreeter()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        greeter.Greet()
    }
}

ベンチマーク結果

一番、右側が1回呼び出し辺りの処理時間。

go-dynoを使った方を平均650ns、使わない方を平均0.25nsとすると約2600倍、go-dynoを使った方が遅いということになる(雑なベンチだけども・・・)

❯ go test -bench=. -benchtime=10000x -count=3
goos: linux
goarch: amd64
pkg: github.com/miyazi777/go-dyno-sample1
cpu: 11th Gen Intel(R) Core(TM) i7-11700K @ 3.60GHz
BenchmarkToDynamicGreeter-16               10000               624.0 ns/op
BenchmarkToDynamicGreeter-16               10000               670.2 ns/op
BenchmarkToDynamicGreeter-16               10000               645.5 ns/op
BenchmarkToStaticGreeter-16                10000                 0.2243 ns/op
BenchmarkToStaticGreeter-16                10000                 0.2302 ns/op
BenchmarkToStaticGreeter-16                10000                 0.2295 ns/op
PASS
ok      github.com/miyazi777/go-dyno-sample1    0.024s

go-dynoを使った方が遅いだろうな、というのは予想どおりではあるものの、想像より差があった。

まとめ

横断的な処理を差し挟みたい場合は以下のように判断して良さそう。

  • テストコードでの使用、ツールでの使用、そこまで処理性能を重視していない・上記のようなオーバーヘッドが許容できるのであれば、go-dynoは良い選択肢になると思う
  • 処理性能を重視したい場合で横断的な処理を挟みたい場合はgo-wrapなどコード生成で対応するのが良さそう

github.com

Go言語でテストケースからだけ呼び出しされている処理はビルドファイルに含まれるのか?

概要

Goで開発をしていて、どこまでがビルド対象なのか? が気になったので調査した。

テストコードの_testのサフィックスが付いたファイルはビルド対象にはならない、ということはなんとなく知っていたが、それ以外は?

例えば、gomockという有名なモックライブラリがあるが、このライブラリはモックをinterfaceから自動生成する機能を持っている。 自動生成されたモックは当然テスト時にしか使用しない。

これらはビルドに含まれるのか?

結論

アプリケーションから呼び出しがあるもの以外は含まれない、が結論になる

検証

テストコードからしか参照されていないケース

検証用コードの作成

今回の検証ではtest_util.goに定義されている定数TestStrがビルド対象となるかを検証していきます。 test_util.goのTestStrはmain_test.goからしか参照されていないコードを作成します。

ファイル構成

main.go
main_test.go
testutil/test_util.go

main.go

package main

func main() {
    fmt.Println("main")
}

main_test.go

package main

func Test(t *testing.T) {
    fmt.Println(testutil.TestStr)
}

testutil/test_util.go

package testutil

var TestStr = "testutil"

検証

検証にはnmというコマンドを使用。 これはオブジェクト内のシンボルを表示することができるツールで、今回の検証にはうってつけ。

以下のようにgoの実行ファイルを作成後、nmコマンドで実行して検証。

> go build -o bin

> nm bin | grep testutil

>

testutilがビルドに含まれているのであれば、シンボルとしてtestutilが表示されるハズだが、今回は表示されていないので、このバイナリには含まれていないことがわかった。

アプケーションコードとテストコードの両方から参照されているケース

検証用のコードの修正

main.goからtest_util.goの定数を参照するように修正。

main.go

package main

func main() {
    fmt.Println("main")
    fmt.Println(testutil.TestStr)
}

検証

同じようにビルド後にnmコマンドを実行する。

> go build -o bin

> nm bin | grep testutil
000000000055afe0 D test1/testutil.TestStr

今回はシンボルとしてtestutilの定数が含まれていることがわかった。 つまりアプリケーションとして必要だから定数に含まれるようになった。

コンパイラを作る人たちには当たり前の話しかもしれないが、こうして検証してみるといろいろとわかって楽しい。

参考

今回のことはほぼ、以下のstack overflowに記載されていたことを少し修正して試したが、大変参考になった。

stackoverflow.com

GoでのAOP(aspect oriented programming)的なことを実現する方法

概要

タイトルどおり、Go言語でAOP(aspect oriented programming)的なことを実現する方法を考えてみました。 JavaでSpringフレームワークで実現されているAOPpythonのデコレータをイメージしていただけたらと思います。

具体的な実現方法

思いついた方法は以下の3つでした。

  • ラッパー関数で実現
  • リフレクションで実現
  • ツールで実現

以下で処理の前後にログを差し挟む、という処理を実現する方法を検証してみました。

ラッパー関数で実現

機能を付与したい構造体と同じinterfaceを満たす実装で実現する。 一番シンプルだが、機能を付与したい構造体の全てのinterfaceを満たすのは面倒であまり実用的でないように思う。

package main

import "fmt"

func main() {
    w := WrapProcess(NewProcess())
    w.Execute()
    // Output:
    //   Before
    //   Executing process...
    //   After
}

// 実現したい処理
type Process interface {
    Execcute()
}

func NewProcess() Process {
    return &process{}
}

type process struct{}

func (p *process) Execcute() {
    fmt.Println("Executing process...")
}

// wrapper
type Wrapper struct {
    wrappedProcess Process
}

func WrapProcess(p Process) Wrapper {
    return Wrapper{
        wrappedProcess: p,
    }
}

func (w *Wrapper) Execute() {
    fmt.Println("Before")
    w.wrappedProcess.Execcute()
    fmt.Println("After")
}

リフレクションで実現

リフレクションを使用することで上記のラッパー関数を用意する方法に比べたら、どこにでも適用しやすい分実用的だとは思う。 少なくともpythonとデコレータ相当のことはこれで実現できそう。 ただし、リフレクションを使用しているので毎回呼び出される場合のオーバーヘッドが許容できるかどうかは作成するアプリによる。

package main

import (
    "fmt"
    "reflect"
)

func wrap[T any](fn T) T {
    rfn := reflect.ValueOf(fn)

    // 関数でない場合はpanic
    if rfn.Kind() != reflect.Func {
        panic("fn should be a function")
    }

    // 同じ引数・戻り値を持つ関数を作成
    wfn := reflect.MakeFunc(rfn.Type(), func(args []reflect.Value) []reflect.Value {
        fmt.Println("before")
        result := rfn.Call(args)
        fmt.Println("after")
        return result
    })
    return wfn.Interface().(T)
}

func main() {
    wrap(display)()      // こんにちは、世界!
    wrap(greet)("山田")   // こんにちは、山田さん! 
    wrapedAdd := wrap(add)
    fmt.Println("result", wrapedAdd(5, 3)) // 8
}

func display() {
    fmt.Println("こんにちは、世界!")
}

func greet(name string) {
    fmt.Printf("こんにちは、%sさん!\n", name)
}

func add(a, b int) int {
    return a + b
}

参考にした記事

zenn.dev

ツールで実現

ライブラリやツールがないかと探し出したところ、gowrapというコード生成ツールがあり、使えそうだった。

github.com

このツールは横断的に適用したい処理をテンプレートとして記述し、適用対象のinterfaceをCLIツールで指定してコード生成するツール。 詳しい使い方は以下に記述。

使った感想としては本格的なアプリを作る場合、上記の2つの手段に比べて一番実用的に感じた。 ただ、最初にテンプレート用の独自の関数を知っておく必要があるが、それについては特にキチンとしたドキュメントが用意されているわけではない為、最初はテンプレート作成に苦労するかも。

使い方

インストール

CLIとしていストールしたかったので下記のコマンドを実行。

go install github.com/hexdigest/gowrap/cmd/gowrap@latest

テンプレート作成

gowrapにはすでにいくつかのテンプレートが作成されていたので、それを元に下記のようなテンプレートを作成。 元々の処理の呼び出し前後で"Before"と"After"を出力するだけのシンプルな処理のテンプレート。

./gowrap/log_template.txt

import (
  "io"
  "fmt"
)

{{ $decorator := (or .Vars.DecoratorName (printf "%sWithLog" .Interface.Name)) }}

// {{$decorator}} implements {{.Interface.Type}} that is instrumented with logging
type {{$decorator}} struct {
  _base {{.Interface.Type}}
}

// New{{$decorator}} instruments an implementation of the {{.Interface.Type}} with simple logging
func New{{$decorator}}(base {{.Interface.Type}}, stdout, stderr io.Writer) {{$decorator}} {
  return {{$decorator}}{
    _base: base, 
  }
}

{{range $method := .Interface.Methods}}
  // {{$method.Name}} implements {{$.Interface.Type}}
  func (_d {{$decorator}}) {{$method.Declaration}} {
      fmt.Println("Before")
      {{- if $method.HasResults}}
        {{$method.ResultsNames}} = _d._base.{{$method.Call}}
        fmt.Println("After")
        return {{$method.ResultsNames}}
      {{else}} 
        _d._base.{{$method.Call}}
        fmt.Println("After")
      {{end}} 
  }
{{end}}

テンプレートは正直、慣れないとわかりづらい部分があると思うが、以下のページと既存テンプレートを見ることでなんとなくわかると思う。

pkg.go.dev

生成元になるinterfaceを定義

./gowrap/main.go

package main

type Process interface {
    Execute1()
    Execute2() string
}

テンプレートとinterfaceを元にコード生成

以下のコマンドでいよいよコード生成する。

gowrap gen -p ./gowrap -i Process -t ./gowrap/log_template.txt -o ./gowrap/log_process.go

できたコードは以下のようになる。

// Code generated by gowrap. DO NOT EDIT.
// template: log_template.txt
// gowrap: http://github.com/hexdigest/gowrap

package main

//go:generate gowrap gen -p github.com/miyazi777/test1/gowrap -i Process -t log_template.txt -o log_process.go -l ""

import (
    "fmt"
    "io"
)

// ProcessWithLog implements Process that is instrumented with logging
type ProcessWithLog struct {
    _base Process
}

// NewProcessWithLog instruments an implementation of the Process with simple logging
func NewProcessWithLog(base Process, stdout, stderr io.Writer) ProcessWithLog {
    return ProcessWithLog{
        _base: base,
    }
}

// Execute1 implements Process
func (_d ProcessWithLog) Execute1() {
    fmt.Println("Before")
    _d._base.Execute1()
    fmt.Println("After")

}

// Execute2 implements Process
func (_d ProcessWithLog) Execute2() (s1 string) {
    fmt.Println("Before")
    s1 = _d._base.Execute2()
    fmt.Println("After")
    return s1

}

参考

gowrapを使う際に参考した記事。

www.wantedly.com

その他

実をいうともう1つ方法があるにはある。 コンパイル時に-toolexecオプションを使用することでも同様のことは実現できるっぽい。 実現方法も一番スマートだと思う。 こんなライブラリも存在するぐらい。

github.com

ただし、-toolexecオプション自体に関する情報がなさすぎて、これを使っていいのかどうか。 個人的なプロジェクトで使用する分には全然良いけど、商用のプロダクションコードで使用するには躊躇するぐらい情報がなかった。

goverterを使って構造体間の詰替えが可能か検証

概要

Goでアプリケーションを開発していると構造体から構造体への詰替えは頻繁に発生すると思います。 特に項目数が多いと単調なコードを繰り返し記述することになり、微妙にしんどいなと感じています。

最近は生成AIがあるので多少はマシになったとはいえ、全てケースで正確に生成されるわけでもない為、決定的な解決策ではないと感じています。

この辺り、何か楽できるものはないかと思って調べてみました。 その中でgoverterというツールを見つけたので検証してみました。

公式サイト

goverter.jmattheis.de

githubはこちら。

github.com

大雑把には変換したい2つの構造体を入力と出力に指定した関数を持つinterfaceを定義すると、そこから詰替え処理コードを生成してくれるツールになります。

使い方

以下の3ステップで使えるようになります。

1. インストール

まずは以下のようにinstallコマンドでinstallします。

go install github.com/jmattheis/goverter/cmd/goverter@v1.8.3

2. 変換処理のinterfaceを定義

次にこのツールのユニークな所だと思いますが、変換処理のinterfaceを定義します。 さらに定義したinterfaceにgoverter用のコメントを付与することで、goverterでの変換処理用のinterfaceであることを明示します。

具体的には以下のようなコードを記述することでInput1からOutput1へ変換処理をConverter1というinterfaceを満たす実装を生成します。

sample/sample1.go

package sample

type Input1 struct {
    Name string
    Age  int
}

type Oupput1 struct {
    Name string
    Age  int
}

// goverter:converter
type Converter1 interface {
    Convert(source Input1) Oupput1
}

3. 詰替え処理を生成

コマンドラインから以下のコマンドを実行します。

goverter gen ./sample/.

特に出力先などを指定しない場合、interfaceを定義したファイル直下にgeneratedというディレクトリが作成され、その中にgenerated.goというファイルが生成されます。 中身はこのようになっています。

sample/generated/generated.go

package generated

import sample "github.com/miyazi777/sample1/sample"

type Converter1Impl struct{}

func (c *Converter1Impl) Convert(source sample.Input1) sample.Oupput1 {
    var sampleOupput1 sample.Oupput1
    sampleOupput1.Name = source.Name
    sampleOupput1.Age = source.Age
    return sampleOupput1
}

Input1構造体のNameとAgeの詰替え処理を生成されています。

検証

ここからはいくつか、ありそうなシチュエーションを検証してみました。

無視したい項目がある時

項目の無視は可能。 入力側で無視したい項目がある場合は特に設定は不要。 出力側で無視したい項目がある場合は以下のサンプルのようにgoverter:ignore <項目名>のコメントを追記することで無視することが可能

以下のサンプルではInput2のGenderは無視され、Output2のAddressがignore指定されている為、無視されます。

package sample

type Input2 struct {
    Name   string
    Age    int
    Gender int
}

type Oupput2 struct {
    Name    string
    Age     int
    Address string
}

// goverter:converter
type Converter2 interface {
    // goverter:ignore Address
    Convert(source Input2) Oupput2
}

入力と出力で項目名が違う時

マッピング可能。

goverter:map <入力項目名> <出力項目名>を指定します。

以下サンプル。 下のサンプルではName -> UserNameに名前が違う項目への詰替えが実施されるコードが生成されました。

package sample

type Input3 struct {
    Name string
    Age  int
}

type Oupput3 struct {
    UserName string
    Age      int
}

// goverter:converter
type Converter3 interface {
    // goverter:map Name UserName
    Convert(source Input3) Oupput3
}

型が違う項目を変換したい時

マッピング可能。

goverter:map <入力項目名> <出力項目名> | <変換関数名>を指定する。

これは下のサンプルを見てもらった方がわかりやすいと思います。 int型のAgeからstring型のAgeに変換していて、変換関数にはConvertAgeを指定。

package sample

import "strconv"

type Input4 struct {
    Name string
    Age  int
}

type Oupput4 struct {
    Name string
    Age  string
}

// goverter:converter
type Converter4 interface {
    // goverter:map Age Age | ConvertAge
    Convert(source Input4) Oupput4
}

func ConvertAge(age int) string {
    return strconv.Itoa(age)
}

ネストした構造体を変換したい時

親子、それぞれの構造体を指定することで可能。

これもサンプルを見てもらった方がわかりやすいと思います。 Input5とOutput5、それぞれにDetailというネストした構造体が存在しています。 そして、親の変換処理である、Converter5と子の変換処理である、DetailConverter5を定義することで対応可能となります。

package sample

type Input5 struct {
    Name   string
    Age    int
    Detail Input5Detail
}

type Input5Detail struct {
    DetailName string
    DetailType int
}

type Output5 struct {
    Name   string
    Age    int
    Detail Output5Detail
}

type Output5Detail struct {
    DetailName string
    DetailType int
}

// goverter:converter
type Converter5 interface {
    Convert(source Input5) Output5
}

// goverter:converter
type DetailConverter5 interface {
    Convert(source Input5Detail) Output5Detail
}

非公開フィールドを持つ構造体

残念ながら対応していませんでした。

公式ページには非公開フィールドに関するページはあるのですが、これを読む限りは無視するか、コンストラクタがあるのであればコンストラクタを使用する、ということが案内されており、このツールで対応しないようでした。 https://goverter.jmattheis.de/guide/unexported-field

またissueでもgetterやsetterをサポートについての議論もあったのですが、特にこれがツールに反映されている様子はありませんでした。 https://github.com/jmattheis/goverter/issues/179

非公開フィールドへの対応まであったら、さらに使える幅が広がりそうなのに・・・

最後に

詰替え処理を楽にすることが出来る、1つの選択肢として良さそうなツールだと思いました。

mockioを使ってみた

タイトルどおり、mockioを使ってみただけの記事です。

最近、偶然Xで以下のツイートを見てmockioの存在を知りました。

公式

公式ドキュメントとgithubは以下のとおり。

ovechkin-dm.github.io

github.com

Javaのmockitoにインスパイアされたモックライブラリ、とのこと。 site.mockito.org

お試し

お試し対象のコード

まずはちょっとしたテスト対象となるコードを作成します。 すごくシンプルな処理で単純にユーザー名を返却するだけの処理になります。

ちょっとだけ階層化していて、usecase -> repositoryのような呼び出し順序にしています。

main.go

package main

import (
    "fmt"
    "test1/repository"
    "test1/usecase"
)

func main() {
    userRepo := repository.NewUserRepository()
    usecase := usecase.NewGetUserUsecase(userRepo)
    username := usecase.Execute()
    fmt.Println(username) // UserName
}

usecase/user.go

package repository

type UserRepository interface {
    GetUser() string
}

type userRepository struct{}

func NewUserRepository() UserRepository {
    return &userRepository{}
}

func (r *userRepository) GetUser() string {
    return "UserName"
}

repository/user.go

package repository

type UserRepository interface {
    GetUser() string
}

type userRepository struct{}

func NewUserRepository() UserRepository {
    return &userRepository{}
}

func (r *userRepository) GetUser() string {
    return "UserName"
}

ユニットテスト

usecaseに対してユニットテストを記述し、この中で今回のmockioを試してみようと思います。

usecase/user_test.go

package usecase_test

import (
    "test1/repository"
    "test1/usecase"
    "testing"

    "github.com/ovechkin-dm/mockio/v2/mock"
)

func TestGetUserUsecase_Execute(t *testing.T) {
    ctrl := mock.NewMockController(t)
    m := mock.Mock[repository.UserRepository](ctrl)
    mock.WhenSingle(m.GetUser()).ThenReturn("TestUserName")

    type args struct {
        userRepo repository.UserRepository
    }
    tests := []struct {
        name string
        args args
        want string
    }{
        {
            name: "test",
            args: args{
                userRepo: m,
            },
            want: "TestUserName",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            u := usecase.NewGetUserUsecase(tt.args.userRepo)
            if got := u.Execute(); got != tt.want {
                t.Errorf("GetUserUsecase.Execute() = %v, want %v", got, tt.want)
            }
        })
    }
}

ポイントはテストコードの最初でモックを作成している箇所だと思います。 * NewMockControllerでmock用のコントローラを作成 * モック対象のrepository.UserRepositoryのモックを作成します。 * モックしたい関数を指定、さらに呼び出された時の返却を指定します。

感想

gomockなどに慣れていれば、すぐに理解できると思います。 内部実装をちょっとだけ見てみたのですが、gomockとは違い、genericsをうまく活用してコード生成せずともモックできるようにしてているようでした。

本当に少し触っただけではありますが、使い心地も良さそうなので、もう少し調査した上で仕事にも活用していこうと思いました。

Goのユニットテスト時に非公開フィールドのオブジェクト同士を比較する

ユニットテストを記述する際、go-cmpを使用することは多いと思います。

github.com

ただ、なぜかgo-cmpでは非公開フィールドは比較されないものと思い込んでいました。 これはデフォルトの動作ではpanicが発生していてからです。 しかし、実際には非公開フィールドについてもちゃんと比較可能でしたので、具体的なやり方を残しておきます。

非公開フィールドを比較する

AllowUnexportedオプションを指定することで比較可能になります。 以下、サンプルです。

type User struct {
    name       string
    age        int
    prefecture string
}

// 比較サンプル
func sample1() {
       // 比較オブジェクトが同じ場合
    user1 := User{name: "user1", age: 30, prefecture: "Tokyo"}
    user2 := User{name: "user1", age: 30, prefecture: "Tokyo"}
    result1 := cmp.Diff(user1, user2, cmp.AllowUnexported(User{}))
    fmt.Println(result1) // Should print an empty string

        // 比較オブジェクトが違う場合
    user3 := User{name: "user1", age: 30, prefecture: "Tokyo"}
    user4 := User{name: "user2", age: 32, prefecture: "Tokyo"}
    result2 := cmp.Diff(user3, user4, cmp.AllowUnexported(User{}))
    fmt.Println(result2)
    // Should print:
    //   main.User{
    // -   name:       "user1",
    // +   name:       "user2",
    // -   age:        30,
    // +   age:        32,
    //     prefecture: "Tokyo",
    //   }
    // }
}

オブジェクトが違う場合、違う箇所も見やすく表示してくれて良い感じです。

比較しない項目を指定可能かどうかの検証

ユニットテストでは一部項目を比較したくないケースもあるかと思います。 その場合、go-cmpではIgnoreFieldsを指定することで可能なのですが、これが非公開フィールドでも同様かどうか検証しました。

結果としては非公開フィールドでもignoreFieldsオプションは使用可能でした。 以下、サンプルです。

type User struct {
    name       string
    age        int
    prefecture string
}

// ageとprefectureを無視して比較
func sample2() {
    user1 := User{name: "user1", age: 30, prefecture: "Tokyo"}
    user2 := User{name: "user1", age: 32, prefecture: "Chiba"}
    result1 := cmp.Diff(user1, user2, cmp.AllowUnexported(User{}), cmpopts.IgnoreFields(User{}, "age", "prefecture"))
    fmt.Println(result1) // Should print an empty string
}

ユニットテスト内で使用する

特に工夫なく、そのまま使用します。 testifyパッケージのassert.Emptyと一緒に使用すると良さそうに思います。

assert.Empty(t, cmp.Diff(expected, got, cmp.AllowUnexported(item.Item{}), cmpopts.IgnoreFields(item.Item{}, "itemType")))

cmp.Equalという関数もあり、比較オブジェクトが同じ場合にはtrue、違う場合にはfalseを返却するのですが、違う場合でも何が違うのかがわからない為、ここではcmp.Diffを使用しています。

最後に

このライブラリに限らずですが、何かを使用するのであれば、公式ドキュメントをちゃんとみないと上記のような思い違いをしてしまうことを痛感しました。