Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コード生成なしでモック処理を実現!ovechkin-dm/mockioで学ぶメタプログラミング

 コード生成なしでモック処理を実現!ovechkin-dm/mockioで学ぶメタプログラミング

本セッションでは、コード生成を必要とせずランタイムでモック処理を実現するovechkin-dm/mockio[1]というライブラリを取り上げ、Go言語におけるメタプログラミングの手法について解説します。

モックライブラリは単体テストにおける強力なツールです。
依存モジュールの振る舞いを一時的に置き換え、対象ロジックのテストを容易にします。
uber-go/mock[2]やmatryer/moq[3]といった既存のGoモックライブラリの多くは、事前のコード生成を前提とした設計になっています。
一方、本セッションで取り扱うmockioは、code1に示す通り、コード生成を一切必要とせずに型安全なモック処理を実現する革新的な手法を採用しています。

mockioの根幹を支えるのがovechkin-dm/go-dyno[4]です。
go-dynoは、インターフェース型を渡すとそれを満たす構造体を動的に生成するDynamic関数を提供します。
さらに、生成された構造体のメソッド呼び出し時に任意の処理を発火させるプロキシ関数(code2のCalculatorHandler参照)を渡すことが可能で、これがモック処理の肝になります。

本セッションでは、go-dynoがどのようにしてインターフェースの型情報から、それを満たす構造体を動的に生成しているのか、メソッドの中身を自由に定義することができるのかを深く掘り下げます。
コード生成なしで型安全なメタプログラミングを実現するテクニックについて、generics, reflect, unsafe, assemblyを活用した内部実装を詳細に解き明かします。

また、mockioのモックライブラリとしての価値にも言及します。
コード生成を行わないモックアプローチの機能的な・パフォーマンス的なメリットと課題について、既存モックライブラリと比較・評価します。

Goのランタイムの理解を深めたい方やモックライブラリ開発に興味がある方にとって、このセッションが有益な情報となれば幸いです。

code1: mockioを活用した単体テスト

```
// main.go
package main

type Calculator interface {
Add(a, b int) int
}

func Sum(calculator Calculator, nums ...int) int {
var result int
for _, num := range nums {
result = calculator.Add(result, num)
}
return result
}

// main_test.go
package main

import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/ovechkin-dm/mockio/v2/mock"
)

func Test_Sum(t *testing.T) {
ctrl := mock.NewMockController(t)

// NOTE: コード生成を行わずにモックを実現
mockCalculator := mock.Mock[Calculator](ctrl)
mock.When(mockCalculator.Add(0, 1)).ThenReturn(1)
mock.When(mockCalculator.Add(1, 2)).ThenReturn(3)

result := Sum(mockCalculator, 1, 2)
assert.Equal(t, 3, result)

mock.Verify(mockCalculator, mock.Times(1)).Add(0, 1)
mock.Verify(mockCalculator, mock.Times(1)).Add(1, 2)
}
```

code2: go-dynoによるメタプログラミング
``` go
package main

import (
"fmt"
"reflect"

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

type Calculator interface {
Add(a, b int) int
Sub(a, b int) int
}

// NOTE: ランタイムで生成される構造体が持つメソッドの振る舞いをプロキシとして定義
func CalculatorHandler(m reflect.Method, values []reflect.Value) []reflect.Value {
fmt.Println("Method called:", m.Name)
switch m.Name {
case "Add":
return []reflect.Value{reflect.ValueOf(int(values[0].Int() + values[1].Int()))}
case "Sub":
return []reflect.Value{reflect.ValueOf(int(values[0].Int() - values[1].Int()))}
}
return nil
}

func main() {
// NOTE: Calculator型を満たす構造体をランタイムで生成
dynamicCalculator, _ := dyno.Dynamic[Calculator](CalculatorHandler)

addResult := dynamicCalculator.Add(2, 1) // Output: Method called: Add
fmt.Println(addResult) // Output: 3

subResult := dynamicCalculator.Sub(2, 1) // Output: Method called: Sub
fmt.Println(subResult) // Output: 1
}
````

[1]: https://github.com/ovechkin-dm/mockio
[2]: https://github.com/uber-go/mock
[3]: https://github.com/matryer/moq
[4]: https://github.com/ovechkin-dm/go-dyno

Avatar for QualiArts

QualiArts

October 06, 2025
Tweet

More Decks by QualiArts

Other Decks in Programming

Transcript

  1. ੜ੒ͨ͠ߏ଄ମͷϝιου͕ݺ͹Εͨ࣌ͷॲཧΛࢦఆ͢Δ४උฤ HPEZOPQSPYZQSPYZHP ɾੜ੒ͨ͠ߏ଄ମΛʮTUSVDU5ZQF6ODPNNPOʯʹΩϟετ ɾ.FUIPEςʔϒϧ഑ྻͷҐஔΛׂΓग़͢ NFUIPE1US ࣮ࡍʹݺͼग़͞ΕΔॲཧ TUSVDU5ZQF6ODPNNPO 4USVDU5ZQF 6ODPNNPO5ZQF .FUIPE

    .FUIPE <>.FUIPE NP f ɾ ɾ ɾ *GO 5GO ຊ౰͸ςΩετσΟϨΫςΟϒ͔ΒΦϑηοτܭࢉ 6OTBGF1PJOUFSͰJOUFSOBMͳߏ଄΋औಘͰ͖Δʂ ͜ΕͰϝιουςʔϒϧΛϝϞϦվม͢Δ४උ׬ྃͩʂ
  2. HPEZOPʹΑΔϝλϓϩάϥϛϯά ʙؔ਺ͷੜ੒ͱϝιου͔Βͷݺͼग़͠ʙ ϝιου ؔ਺ ߏ଄ମ Ҿ਺ ίʔυੜ੒ܕͷϞοΫϥΠϒϥϦͷ࢓૊Έ NPDLJPͷ঺հ HPEZOPʹΑΔϝλϓϩάϥϛϯά ɹɾઃܭ֓ཁ

    ɹɾߏ଄ମͷੜ੒ͱϝιου޲͖ઌมߋ ɹɾؔ਺ͷੜ੒ͱϝιου͔Βͷݺͼग़͠ ɹɾؔ਺͕ϝιουͱҾ਺Λڞ༗͢ΔτϦοΫ ͓͸Α͏ͬͯݴ͍͍ͨΆΑʂ
  3. UJQTϨδελͱϝϞϦɺελοΫϑϨʔϜʹ͍ͭͯ ओͳهԱྖҬ͸$16ͱϝϞϦͷͭ ϝϞϦͷ಺༰Λ$16͕ಡΈऔΓɺԋࢉͯ͠ϝϞϦΛ্ॻ͖͢Δ $16 ϝϞϦ UFYU໋ྩηοτ TUBDL ϥΠϑαΠΫϧ͕੩తͳσʔλΛׂΓ౰ͯΔྖҬ EBUBॳظԽࡁΈάϩʔόϧม਺ CTTະॳظԽάϩʔόϧม਺

    IFBQ ϥΠϑαΠΫϧ͕ಈతͳσʔλΛׂΓ౰ͯΔྖҬ ($͕૟আ͢Δͱ͜Ζ Ծ૝ϝϞϦۭؒ Ϩδελ "9 Ϩδελ #9 ʜ ʜ ௿ҐΞυϨε ߴҐΞυϨε ۭ͖ྖҬ ؔ਺ͷݺͼग़࣌͠ɺελοΫʹελοΫϑϨʔϜ͕ੵ·ΕΔɻ ελοΫϑϨʔϜͷج४఺ΛϑϨʔϜϙΠϯλʢ'1ʣͱ͍͏ɻ MPDBMWBSJBCMFϩʔΧϧม਺ HPTSDSVOUJNFTUBDLHP ελοΫϑϨʔϜ '1 'SBNF1PJOUFS 41 4UBDL1PJOUFS BSHTGSPNDBMMFSҾ਺ SFUVSOBEESFTTؔ਺ऴྃ࣌ͷδϟϯϓઌ DBMMFS`T#1ؔ਺ऴྃ࣌ 41΍'1ΛͲ͜ʹ໭ͤ͞Δ͔
  4. UJQTؔ਺ͱϝιουʹ͓͚ΔϨδελঢ়ଶͷྨࣅ఺ ʜ ؔ਺ͱϝιουͷϨδελঢ়ଶΛݟൺ΂Δͱͱͯ΋ࣅ͍ͯΔ ࠩ෼͸Ϩδελͷͭ໨ʹϨγʔόʔܕ͕͋Δ͔Ͳ͏͔ $16 Ϩδελ"9 Ҿ਺B Ϩδελ#9 Ҿ਺C ʜ

    $16 Ϩδελ"9 ϨγʔόʔU Ϩδελ#9 Ҿ਺B Ϩδελ$9 Ҿ਺C IUUQTHJUIVCDPNLBSBNBSVBMQIBQMBZHSPVOECMPCNBJOHPEZOP3&"%.&NEGVODUJPOT IUUQTHJUIVCDPNLBSBNBSVBMQIBQMBZHSPVOECMPCNBJOHPEZOP3&"%.&NENFUIPET ؔ਺ ϝιου
  5. EZOBNJD'PP#BSʢϝιουʣ ͷελοΫϑϨʔϜ CZUFT $16 EZOBNJD'PP#BSͷୈҾ਺ BSS<>\^ EZOBNJD'PP.FUIPET<>ʢؔ਺ʣ ͷελοΫϑϨʔϜ ϝϞϦ ελοΫʣ

    Ϩδελ#9 EZOBNJD'PP#BSͷୈҾ਺ OVN Ϩδελ"9 SFDFJWFSEZOBNJD'PP ɾϝιουˠؔ਺ͷݺͼग़͠Λߟ͑Δɻ ɾϝιου͸ؔ਺ʹҾ਺Λ౉ͤͳ͍ ɾ݁ՌɺελοΫϑϨʔϜ͸࿈ଓ͢ΔܗʹͳΔ ϝιου ؔ਺ Ҿ਺ͳ͠ ຊདྷ͜͜ʹҾ਺ΛΞϩέʔγϣϯͯ͠఻ൖͯ͠ཉ͍͠ˠ ϝιου͔Βؔ਺ʹҾ਺͸౉ͤͳ͍ɻͲͷΑ͏ʹࢀর͢Δʁ
  6. EZOBNJD'PP#BSʢϝιουʣ ͷελοΫϑϨʔϜ CZUFT $16 EZOBNJD'PP#BSͷୈҾ਺ BSS<>\^ EZOBNJD'PP.FUIPET<>ʢؔ਺ʣ ͷελοΫϑϨʔϜ ɾ͜ͷঢ়ଶͰؔ਺Λ࣮ߦ͢ΔͱɺҾ਺͕ਖ਼ৗʹऔಘͰ͖ͳ͍ ɾ͍͍ײ͡ʹϝιουݺͼग़࣌͠ͷҾ਺Λ೷͖ݟ͍ͨ͠Μ͚ͩͲʜ

    '1 'SBNF1PJOUFS ˠͲ͏ʹ͔ͯ͠OVN͕Ϩδελ#9ΛɺBSS͕'1 Λࢀর͢ΔΑ͏ʹͰ͖ͳ͍͔ʁ ͜ΕΛεΩοϓ͍ͨ͠ ͜͜ΛεΩοϓ͍ͨ͠ ϝϞϦ ελοΫʣ Ϩδελ#9 EZOBNJD'PP#BSͷୈҾ਺ OVN Ϩδελ"9 SFDFJWFSEZOBNJD'PP OVN͸Ϩδελ"9ʹɺBSS͸'1 ʹ͋ΔͱࢥͬͯΔ͚Ͳɺ࣮ࡍ͸ͦ͏͡Όͳ͍Έ͍ͨΆΑʜ ϝιου͔Βؔ਺ʹҾ਺͸౉ͤͳ͍ɻͲͷΑ͏ʹࢀর͢Δʁ
  7. EZOBNJD'PP#BSʢϝιουʣ ͷελοΫϑϨʔϜ CZUFT $16 EZOBNJD'PP#BSͷୈҾ਺ BSS<>\^ EZOBNJD'PP.FUIPET<>ʢؔ਺ʣ ͷελοΫϑϨʔϜ ؔ਺ʹࢀর͠ͳ͍Ҿ਺Λ༨෼ʹͭ౉͢ ɾୈҾ਺ʹJOUΛ௥ՃɻϨδελ"9ΛεΩοϓ

    ɾୈҾ਺ʹ<>JOUΛ௥ՃɺελοΫΛόΠτεΩοϓ '1 'SBNF1PJOUFS '1  Ϩδελ"9ΛεΩοϓ ελοΫΛόΠτεΩοϓ ϝϞϦ ελοΫʣ ୈҾ਺͔ΒҾ਺͕ਖ਼ৗʹࢀরͰ͖ΔΑ͏ʹͳͬͨΆΑʂʂʂ👏 ୈҾ਺Ҏ߱ɺؔ਺͕ϝιουͱҾ਺Λڞ༗͢Δ͜ͱʹ੒ޭʂ👏 Ϩδελ#9 EZOBNJD'PP#BSͷୈҾ਺ OVN Ϩδελ"9 SFDFJWFSEZOBNJD'PP ϝιου͔Βؔ਺ʹҾ਺͸౉ͤͳ͍ɻͲͷΑ͏ʹࢀর͢Δʁ