テスト可能なGoコードの作成


テスト分類


開発に関わるテストは主に以下の3種類あり、その他の範囲の広いテスト、例えばシステムテスト、機能テストはここでは紹介しない.
  • unit test(ユニットテスト):プログラムモジュールに対して正確性検査を行うテスト作業であり、プログラムユニットはアプリケーションの最小テスト可能部品である.ユニットテストでは、外部依存性がないことが要求されます.
  • integration test(統合テスト):組立テストまたは連合テストとも呼ばれます.ユニットテストに基づいて、すべてのモジュールを設計要求に従ってサブシステムまたはシステムに組み立て、統合テストを行う.統合テストは、通常、db、mq、バックエンドインタフェースなどの実際の依存にアクセスします.
  • e 2 e test(エンドツーエンドテスト):ソフトウェア全体と外部インタフェースとの統合を最初から最後まで検証します.たとえば、Postmanを使用してserverインタフェースをテストします.

  • テストの前提条件

  • モジュール化:論理が明確で、それぞれの責任を負う
  • 細粒度:関数式、注目点入出力
  • 階層:MVC境界ははっきりしていて、ボールを持っているのではなく、
  • 抽象:
  • を実装するのではなく、インタフェースに依存する
  • 依存注入:mockに置き換えて
  • を実現するのに便利である.
    これらの概念はJavaのように見えますが、確かに貴重なエンジニアリング実践であり、巨人の肩に立つことを学ばなければなりません.

    テストメソッド


    mockインタフェース


    インタフェースに対してmockを行い、インタフェース関数の入力と出力だけに注目し、詳細を実現する必要はありません.
    例:gomock
    gomockは、コード内のinterfaceインタフェースに基づいてstubコードを生産する.注入依存後、EXPECTを使用して入力と出力を事前にレイアウトし、呼び出し元の動作が予想と一致するかどうかをテストします.
    user.go:
    package user
    
    //go:generate mockgen -destination mock_user/mock_user.go github.com/win5do/golang-microservice-demo/docs/sample/gomock/user Index
    type Index interface {
        Get(key string) interface{}
    }
    
    func GetIndex(in Index, key string) interface{} {
      // business logic
      r := in.Get(key)
      // handle output
      return r
    }

    mock_user.go:
    // Code generated by MockGen. DO NOT EDIT.
    // Source: github.com/win5do/golang-microservice-demo/docs/sample/gomock/user (interfaces: Index)
    
    // Package mock_user is a generated GoMock package.
    package mock_user
    
    import (
        gomock "github.com/golang/mock/gomock"
        reflect "reflect"
    )
    
    // MockIndex is a mock of Index interface
    type MockIndex struct {
        ctrl     *gomock.Controller
        recorder *MockIndexMockRecorder
    }
    
    // MockIndexMockRecorder is the mock recorder for MockIndex
    type MockIndexMockRecorder struct {
        mock *MockIndex
    }
    
    // NewMockIndex creates a new mock instance
    func NewMockIndex(ctrl *gomock.Controller) *MockIndex {
        mock := &MockIndex{ctrl: ctrl}
        mock.recorder = &MockIndexMockRecorder{mock}
        return mock
    }
    
    // EXPECT returns an object that allows the caller to indicate expected use
    func (m *MockIndex) EXPECT() *MockIndexMockRecorder {
        return m.recorder
    }
    
    // Get mocks base method
    func (m *MockIndex) Get(arg0 string) interface{} {
        m.ctrl.T.Helper()
        ret := m.ctrl.Call(m, "Get", arg0)
        ret0, _ := ret[0].(interface{})
        return ret0
    }
    
    // Get indicates an expected call of Get
    func (mr *MockIndexMockRecorder) Get(arg0 interface{}) *gomock.Call {
        mr.mock.ctrl.T.Helper()
        return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockIndex)(nil).Get), arg0)
    }

    user_test.go:
    func TestGetIndex(t *testing.T) {
        ctrl := gomock.NewController(t)
        index := mock_user.NewMockIndex(ctrl)
        in := "uid"
        out := "username"
    
        index.EXPECT().Get(in).Return(out)
    
        r :=  GetIndex(index, "uid")
        require.Equal(t, out, r)
    }

    これは最も簡単な例で、何も見えないかもしれません.考えてみれば、GetIndexの論理が非常に複雑であれば、ここでmockはGetを呼び出す入出力に依存せず、GetIndexコードを単一測定で100%上書きすることができる.
    サンプルコード:https://github.com/win5do/go-...
    特徴:賢いやり方で、mock後にインタフェースを呼び出すのは完全に透明で、入出力を準備して、テーブル駆動テストを通じて、各種の境界値をカバーすることができます.testでのみ使用でき、呼び出すたびにmock入出力が必要で、使用はやや煩雑です.

    fake実装


    fakeとは、シミュレーションの真の依存を簡略化し、外部システムの依存を遮断することを指す.
    例:client-go fake client
    Client-goはetcdに依存し、そのシミュレーションはfake-clientパッケージを実現し、メモリ内でリソースを追加削除して調べることを実現し、実際の依存etcd-clientと高度に一致している.
    特徴:シミュレーションは符号化作業量が大きく、fakeコードも正確性を保証するためにテストする必要があるが、完備した後、使用が便利である.

    統合テスト


    テスト環境の実際の依存性を直接使用します.現在dockerなどのコンテナ技術による導入依存は非常に便利であり、データ初期化を行った後、テストコード接続テストdbを構成して統合テストを実行する.
    docker-composeを使用してmysql,mongodb,etcdなどの依存をローカルに起動するには、私の別のプロジェクト:db-localを参照してください.
    特徴:真実依存はオンライン環境と一致性が高く、バグを発見しやすい.しかし、比較的重く、起動が遅く、テストの実行時間が長い.