[ Go ] Go言語で DI してみる (テストコード付き)

この記事は約7分で読めます。

今回はGo言語で DI を実現して、

DIする時と、しない時を比較していきます。

クリーンアーキテクチャでも、同じような実装の仕方をするので、

DIのメリットを、コードベースで説明していきます。

概要

  • DIとは依存性の注入と言い、依存しているオブジェクトとの結合度を下げる
  • テストが書きやすくなる(依存オブジェクトに対して責務を負わない)

DI とは

DIとは依存性の注入です(Dependency Injection)

 

「依存している物を外部から注入する」ことです。

すなわち、結合度を下げることです

メリットとデメリットを見た上で、コードで理解していきましょう!

メリット

  • 依存している物に影響されなくなる=>結合度を下げる
  • 結合度が下がり、テストしやすくなる

依存している物に影響されなくなると、

依存しているクラスや、変数、メソッドの完成を待たずとも、開発ができます!

コンポーネントテストをする際に、引数にすることで、Mockとして導入でき、テストがし易くなります!

コードを見ながらDIを理解する

作るもの: userのBMIを計算し、それを表示するプログラム

userの構造体(身長と体重データあり)をBMI計算機で、BMIを算出してもらい、それを表示します。

ファイル構成

.
├── Makefile
├── calculator.go
├── calculator_test.go
├── main.go
├── mock_calculator.go
├── user.go
└── user_test.go

最後にコードを配布するので、是非ダウンロードして、見てみてください

user.go

ここでは、ユーザの構造体と、ユーザがBMI計算機にデータを入れてBMIを取得し,小数第一位で四捨五入した,文字列を返す操作(getBMI)を書いています

type user struct{
	name string
	height float64
	weight float64
}
func (u user)getBMI()string{
	c := newCalculator()
	return fmt.Sprintf("%s's BMI is %.1f", u.name,c.calcBMI(u.height,u.weight))
}

func (u user)getBMI2(ic ICalculator)string{
	return fmt.Sprintf("%s's BMI is %.1f", u.name,ic.calcBMI(u.height,u.weight))
}

 

getBMI()は依存性の高いコードです。

これは、BMI計算機のオブジェクトをメソッド内で作成し、BMI計算機の計算を行なっています。

calcBMIが実装されないと、このメソッドのテストを行うことができません。

 

getBMI2()はDIしているコードです。

BMI計算機の計算するメソッドだけを定義したインターフェースを引数に入れています。

これによって、calcBMIを実装したICalculatorが入るので、そこの責任を持たずに、BMI結果の表示することだけに集中できます。



calculator.go

type ICalculator interface{
	calcBMI(height ,weight float64)float64
}

type calculator struct{}

func newCalculator()calculator{
	return calculator{}
}

func (calculator)calcBMI(height ,weight float64)float64{
	if height <=0 || weight <= 0{
		return 0.0
	}
	return weight/(height *height)
}

calculatorはICalclatorインターフェースを実装したオブジェクトです。

user.goに必要なBMI計算機の処理をまとめています。

 

テスト本当にしやすいの?

func TestGetBMI(t *testing.T){
  tCases := []struct{
    user user
    expected string
  }{
    {user{"user1",1.75,70},"user1's BMI is 22.9"},
    {user{"user2",1.66,52},"user2's BMI is 18.9"},
  }
  for _,tc := range tCases{
    acutual := tc.user.getBMI()
    if acutual != tc.expected{
      t.Errorf("expected:%v ,acutual:%v",tc.expected,acutual)
    }
  }
}

func TestGetBMI2(t *testing.T){
  tCases := []struct{
    username string
    userBmi float64
    expected string
  }{
    {"user1",18.477,"user1's BMI is 18.5"},
    {"user2",18.922,"user2's BMI is 18.9"},
  }

  ctrl := gomock.NewController(t)
  defer ctrl.Finish()

  for _ , tc := range tCases{
    calclator := NewMockICalculator(ctrl)
    calclator.EXPECT().calcBMI(gomock.Any(),gomock.Any()).Return(tc.userBmi)
    user := user{
      name:tc.username,
      height:0,
      weight:0,
    }
    acutual := user.getBMI2(calclator)
    if acutual != tc.expected{
      t.Errorf("expected:%v ,acutual:%v",tc.expected,acutual)
    }
  }
}

getBMI()とgetBMI2()のテストの比較です!

 

依存性の高いgetBMI()のテストは、

BMI計算機オブジェクトをメソッド内で定義しているため、テストを通すために、先にcalcBMI()の実装する必要があります。

 

一方で、DIしたgetBMI2()のテストは、
calcBMI()の処理をモック化することで、calcBMI()の実装無くしても、getBMI2()で期待する(小数第一位に変換し、結果を返す)処理のテストが書けます

 

テストの書き方はこちら

[ testing ] Go言語入門~ テスト ~
Go言語で書いたコードを テスト します。まず、基本的なコンポーネントテスト、サーバ、コントローラーのテスト,mockを用いたテストの書き方を説明しています。最後にカバレッジ出力についてもまとめています。

コード

DIするGOのコード

プログラミング独学には Udemy が良い (オススメ動画あり)
プログラミング独学で学びたい!けどどう学べばいいかわからないし、プログラミングスクールは高いし、 と考えている人にオススメ!『Udemy』の体験記!オススメの動画も最後にまとめています!是非使ってみてください!

まとめ

今回は、DIをGo言語で実装しました。

DIの考え方はJava Spring Bootでは一般的に使います。

テストを書く上では、依存度を緩くするのは重要です。

クリーンアーキテクチャでも、使われているので、是非DIしていきましょう!!

参考文献

猿でも分かる! Dependency Injection: 依存性の注入

タイトルとURLをコピーしました