みなさんはコーディングする時に、interfaceを使用してますでしょうか?
もし使用してなかった場合に、是非読んでもらえたらと思います。interfaceが何で大事かというのを書いております。今回はGoで書いていますが、他の言語でも同じように大事です。
利点
- 依存関係を逆転させることで、テストがしやすくなる(DIP:依存関係逆転の原則)
- 他のオブジェクトに置き換える(データの取得をテキストからDBに変更する等)必要ができた時に、他のソースに影響なく置き換えられる(LSP:リスコフの置換原則)
例.
例えば、外部のAPIから取得した結果を集計する処理を実装した場合を想定してみると、取得処理の部分をinterfaceを使わずに実装をしてしまうと、外部APIが接続できなと、結果を集計する処理のテストが難しくなります。
interfaceを使うことで、外部APIを接続するところをテスト用にモックに代えてあげてテストを可能にすることができる。
集計処理
// この構造体を集計する
type Data struct {
ProductID string `json:"id"`
Count int64 `json:"count"`
}
// プロダクトIDごとに数を集計する
func CalcDatas(c clientInterface) map[string]int64 {
datas := c.getDatas()
// ProductIDごとにCountを集計するためのマップ
countByProductID := make(map[string]int64)
for _, data := range datas {
countByProductID[data.ProductID] += data.Count
}
return countByProductID
}
interface
type clientInterface interface {
getDatas() []Data
}
外部APIに接続してデータを取得(getDatasの実装は省略)
type client struct{}
func (c client) getDatas() []Data {
// 外部APIを呼び出す処理など、単体テストが難しい処理をしている想定
}
データを取得するモック
type clientForTest struct{}
func (c clientForTest) getDatas() []Data {
datas := []Data{
{ProductID: "a1", Count: 5},
{ProductID: "a2", Count: 3},
{ProductID: "b1", Count: 1},
{ProductID: "a1", Count: 2},
}
return datas
}
テストコード
func TestCalcDatas(t *testing.T) {
datas := CalcDatas(clientForTest{})
expect := make(map[string]int64)
expect["a1"] = 7
expect["a2"] = 3
expect["b1"] = 1
// 計算結果が正しいかを確認
if diff := cmp.Diff(datas, expect); diff != "" {
t.Errorf(diff)
}
}
このようにテストしずらい箇所を、モックに置き代えられるようにinterfaceにしておくことで、テストを簡単にできるようにしている。
また将来このデータをAPI以外の方法で取得する処理になっても、計算の処理には影響なく変更が可能になります(LSP)。
まとめ
interfaceについて今回は記事を書いていきました。
これで読者の方もinterfaceの大切さに気づいてもらえたと思います。
今回はモックを手動で書いてしまいましたが、実際にはgomockというのを使えば簡単にモックの実装できます。使い方の記事をおすすめ記事のリンクに貼ってますので、是非読んでください。
それ以外にもGoについて書いている記事があるので、そちらも読んでもらえたらと思います。
【おすすめ記事のリンク】
コメント