現在Goで作っているアプリケーションを先輩からアドバイスをもらって、より疎結合に実装できるようになったのでまとめる。(MVCの概念の中で行うことを前提にしています)
何をMock化したいのか?
今回のリファクタリングを通じて思ったのは、「どこの関数をmock化したいのか」をまず考えるべきと言うこと。
今回の例だとcontrollerのuser.setvalue()
をmock化できればCreateUserの単体テストが楽になりそうだ。
userController.go
package controllers
import (
"app/~/models"
"github.com/gin-gonic/gin"
)
func CreateUser(c *gin.Context) {
user := &models.User{}
c.BindJSON(user)
user.SetValue(*user) /* <--------- ここをMock化できたらよさそう*/
c.JSON(200, gin.H{
"result": true,
})
}
どうやってやるのか?
Model側の設定
このuser.setvalue()
はUserModelの関数であるため、UserModelが実装となるinterfaceを定義してあげる必要がある。
userModel.goとuserInterface.go
package models
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func (u *User) SetValue(user User) {
// 具体的な処理
}
package models
type UserInterface interface {
SetValue(user User)
}
これで、userModelの代わりとなるをmockを差し込むためのModel側の準備は完了。
controller側の修正
あとは、mockを使うのか実際のmodelを使うのかをインスタンスを生成するタイミングで差し込んであげればいい(コンストラクタインジェクションともいう。)のでそのための修正をcontrollerに加える。
オブジェクト指向言語であれば、コンストラクタがあるのでそこの引数とインスタンス変数で実現できる。しかし、Goにはこういったものはないので独自にNew関数とインスタンス変数っぽいものを作る。New+〜とするのが推奨されているらしい。
修正後 userController.go
package controllers
import (
"app/~/models"
"github.com/gin-gonic/gin"
)
type UserRepository struct {
User models.UserInterface /* <-- インスタンス変数っぽく扱うもの */
}
/* コンストラクタの代わりになるもの */
func NewUserRepository(user models.UserInterface) *UserRepository {
return &UserRepository{
user,
}
}
func (r *UserRepository) CreateUser(c *gin.Context) {
user := &models.User{}
c.BindJSON(user)
r.user.SetValue(*user) /* <--------- UserRepositoryのUserのSetValueを使用する*/
c.JSON(200, gin.H{
"result": true,
})
}
実際にNewするところ
これでようやくインスタンス生成時に、インターフェースを介して疎結合にモデルかmockを差し込める。
controllerに処理を渡す上位層でNewすればいい。
&models.User{}
をMockにすれば、CreateUser()
の単体テストは楽になるだろう¥
userCnt := controllers.NewUserRepository(&models.User{})
userCnt.CreateUser()
まとめ
Goで疎結合な設計を行うためには、構造体とinterfaceをうまく使う必要がある。