概要
過去に紹介した rspec-mock のように既存の関数のオーバライド的な感じでモックを作成したかったのですが gomock では厳しいことが判明しました
とりあえず gomock などは考えずに golang でどうやって実現できるのかを考えてみました
環境
- macOS 10.15
- golang 1.12.9
テストしたいコード
package main
import (
"fmt"
)
type A struct {
b B
}
type B struct {
key string
}
func (b B) Method(value string) {
fmt.Printf("%s => %s\n", b.key, value)
}
func main() {
b := B{key: "hello"}
a := A{b: b}
a.b.Method("world")
}
この a.b.Method
をテストするにあたって Method
をスタブしたいと思います
DI を使った仕組みに書き換える
B 構造体を DI (dependency injection) を使った仕組みに書き換えます
package main
import (
"fmt"
)
type A struct {
b B
}
type B struct {
key string
Method func(key string, value string)
}
func main() {
m := func (key string, value string) {
fmt.Printf("%s => %s\n", key, value)
}
b := B{
key: "hello",
Method: m,
}
a := A{b: b}
a.b.Method(a.b.key, "world")
}
B 構造体のメンバに関数を持たせることで関数の実装をあとから注入する方法です
こうすることでテスト時には main とは異なる挙動をさせることができるため a.b.Method
のスタブが書きやすくなります
package main
import (
"fmt"
"testing"
)
func TestMethod(t *testing.T) {
// stub
m := func(key string, value string) {
fmt.Printf("%s => %s\n", key, value)
fmt.Println("test func")
}
b := B{
key: "hello",
Method: m,
}
a := A{b: b}
a.b.Method(a.b.key, "world")
}
ただこの場合は冒頭に紹介した B 構造体の仕組みから大きく変える必要があるため既存のロジックにも影響する可能性があります
既存のメソッドのオーバライドはできないか
新しい構造体を作成して B 構造体を組み込んで Method をオーバライドする的なことはできますが B 構造体が持つ Method 自体を変更することは golang ではできなさそうです
Ruby でいうところの「クラスの展開」や「特異メソッド」のように既存のクラスに対する実装の変更などはできないようです (そもそも golang にはクラスの概念はありませんが)
BInterface を使う
これも既存のロジックを書き換えてしまいますが A 構造体が持っていた B 構造体を B インタフェースに変えることであたかも B 構造体のように振る舞う fakeB 構造体をフィールドのセットします
こうすることで関数の振る舞いを上書きしているように見せかけます
package main
import (
"fmt"
)
type A struct {
b BInterface
}
type BInterface interface {
Method(value string)
}
type B struct {
key string
}
func (b B) Method(value string) {
fmt.Printf("%s => %s\n", b.key, value)
}
type fakeB struct {
key string
}
func (b fakeB) Method(value string) {
fmt.Printf("%s => %s !!\n", b.key, value)
fmt.Println("by fake")
}
func main() {
b := B{
key: "hello",
}
fb := fakeB{
key: "hello",
}
a := A{b: b}
a.b = fb
a.b.Method("world")
}
一度 B 構造体をセットしているにも関わらずあとからセットしている fakeB 構造体で上書きされているのがわかります
A 構造体を書き換えるのでかなりリスクがある感じはしますが一応これでも対応できました
最後に
golang で構造体が持つ関数をスタブする方法を考えてみました
一番良さそうなのは DI を使う方法かなと思います
自由度も高いのでいろいろなところで使える技だと思います
0 件のコメント:
コメントを投稿