Mokeryを使った模擬データベースユニットテスト



導入
皆さん、この記事では、プロジェクトの依存性をテストするためにmockeryを使用して、Goで単位テストを作成する方法を学びます.私たちは多くの場合、モジュールのような別の依存関係を持つ関数をテストするために模擬テストを使用するので、私たちはどのようにリファクタリングを作成し、ユニットテストを作成し、mockery自体を使用して、手順でステップで書いた例を使用して作成する方法を学ぶことが、それを開始しよう!

なぜモックリー?
mockeryはmock関数を生成するコマンドを用意しています.これは単体テストのために使うことができますので、結果としてmock関数をゼロから書く必要はありません
このチュートリアルをより簡単にするために、我々は最初の記事から最初に我々のコードをリファクタリングする準備をします

リファクタ
まず第一に、我々は古いコードを見に行くつもりです、我々はそれのインターフェースを定義するつもりです、どのように我々はそれをしますか?名前の新しいルートフォルダを作成することによってrepository , ここから、私たちがデータベース依存関係を使用するとき、これは私たちが呼ぶものです.
- config
 |_ config.go
- models
 |_ payment.go
- repository
 |_ payment.go
- test.go
インターフェイスを定義することから始めますpayment.go リポジトリフォルダ内
type IPaymentRepository interface {
    UpdatePayment(id string, payment models.Payment) (models.Payment, error)
    DeletePayment(id string) (int64, error)
    SelectPaymentWIthId(id string) (models.Payment, error)
    CreatePayment(payment models.Payment) (int64, error)
}
データベース依存性を関数から分離するために、データベース依存関係を保持するために
type Repository struct {
    Database *gorm.DB
}
それから、我々はメインのもの以外のすべての機能を動かしますpayment.go , 完全なリポジトリは次のようになります
package repository

import (
    "errors"

    "github.com/yanoandri/simple-goorm/models"
    "gorm.io/gorm"
)

type IPaymentRepository interface {
    UpdatePayment(id string, payment models.Payment) (models.Payment, error)
    DeletePayment(id string) (int64, error)
    SelectPaymentWIthId(id string) (models.Payment, error)
    CreatePayment(payment models.Payment) (int64, error)
}

type Repository struct {
    Database *gorm.DB
}

func (repo Repository) UpdatePayment(id string, payment models.Payment) (models.Payment, error) {
    var updatePayment models.Payment
    result := repo.Database.Model(&updatePayment).Where("id = ?", id).Updates(payment)
    if result.RowsAffected == 0 {
        return models.Payment{}, errors.New("payment data not update")
    }
    return updatePayment, nil
}

func (repo Repository) DeletePayment(id string) (int64, error) {
    var deletedPayment models.Payment
    result := repo.Database.Where("id = ?", id).Delete(&deletedPayment)
    if result.RowsAffected == 0 {
        return 0, errors.New("payment data not update")
    }
    return result.RowsAffected, nil
}

func (repo Repository) SelectPaymentWIthId(id string) (models.Payment, error) {
    var payment models.Payment
    result := repo.Database.First(&payment, "id = ?", id)
    if result.RowsAffected == 0 {
        return models.Payment{}, errors.New("payment data not found")
    }
    return payment, nil
}

func (repo Repository) CreatePayment(payment models.Payment) (int64, error) {
    result := repo.Database.Create(&payment)
    if result.RowsAffected == 0 {
        return 0, errors.New("payment not created")
    }
    return result.RowsAffected, nil
}
すべての関数をリポジトリフォルダーに移動させるので、関数を呼び出す方法を変更する必要がありますtest.gomain 機能
repo := repository.Repository{Database: db}
そして、そこから、我々のパラメタの中にどんな外部の依存関係も置くことなく、我々の機能を使うことができます
// create a payment
payment := models.Payment{
    PaymentCode: "XXX-1",
    Name:        "Payment for item #1",
    Status:      "PENDING",
}

result, err := repo.CreatePayment(payment)

単体テストファイルの作成
幸いにもVSCodeを使用する場合は、プレスによって自動的にユニットテストを生成できますctrl + p 場合は、Windowsまたはcommand + p そして、あなたは以下のスクリーンショットのような機能を生成するオプションを見つけるでしょう

そして、この例では、CreatePayment 関数全体をブロックして生成する
package repository

import (
    "testing"

    "github.com/yanoandri/simple-goorm/models"
)

func TestRepository_CreatePayment(t *testing.T) {
    type args struct {
        payment models.Payment
    }
    tests := []struct {
        name    string
        repo    Repository
        args    args
        want    int64
        wantErr bool
    }{
        // TODO: Add test cases.
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := tt.repo.CreatePayment(tt.args.payment)
            if (err != nil) != tt.wantErr {
                t.Errorf("Repository.CreatePayment() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("Repository.CreatePayment() = %v, want %v", got, tt.want)
            }
        })
    }
}

模擬
我々は、コマンドを使用してmockeryをインストールします
go install github.com/vektra/mockery/v2@latest
それが終了した後、我々は、このコマンドを入力することにより、すべての機能の塊を生成するためにmockery機能を使用して我々の模擬機能を作成します
mockery --all --keeptree
注意mocks 当社のプロジェクトのフォルダがインストールされており、すべての機能がモックされている

そして、これは我々の1つの例ですCreatePayment 模擬関数
// CreatePayment provides a mock function with given fields: payment
func (_m *IPaymentRepository) CreatePayment(payment models.Payment) (int64, error) {
    ret := _m.Called(payment)

    var r0 int64
    if rf, ok := ret.Get(0).(func(models.Payment) int64); ok {
        r0 = rf(payment)
    } else {
        r0 = ret.Get(0).(int64)
    }

    var r1 error
    if rf, ok := ret.Get(1).(func(models.Payment) error); ok {
        r1 = rf(payment)
    } else {
        r1 = ret.Error(1)
    }

    return r0, r1
}

我々のテスト機能を書く
我々のユニットテストに我々のmockked機能を含めましょう
package repository

import (
    "errors"
    "testing"

    mocks "github.com/yanoandri/simple-goorm/mocks/repository"
    "github.com/yanoandri/simple-goorm/models"
)

func TestRepository_CreatePayment(t *testing.T) {
    type args struct {
        payment models.Payment
    }
    tests := []struct {
        name    string
        args    args
        want    int64
        wantErr bool
    }{
        // TODO: Add test cases.
        {
            name: "success_create_payment",
            args: args{
                models.Payment{
                    PaymentCode: "payment-code-011",
                    Status:      "PENDING",
                },
            },
            want:    1,
            wantErr: false,
        },
        {
            name: "failed_create_payment",
            args: args{
                models.Payment{},
            },
            want:    0,
            wantErr: true,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            repo := &mocks.IPaymentRepository{}
            if !tt.wantErr {
                repo.On("CreatePayment", tt.args.payment).Return(tt.want, nil)
            } else {
                repo.On("CreatePayment", tt.args.payment).Return(tt.want, errors.New("Failed to create payment"))
            }
            got, err := repo.CreatePayment(tt.args.payment)
            if (err != nil) != tt.wantErr {
                t.Errorf("Repository.CreatePayment() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("Repository.CreatePayment() = %v, want %v", got, tt.want)
            }
        })
    }
}
Repoが不足していることに注意してください.データベースの実際の依存関係を使用する必要がなかったからです.そして今のところコマンドを使ってテストを実行します
go test ./... -v


結論
これは単体テストでmocks関数を使用する例として1つの関数ですUpdatePayment , DeletePayment そしてSelectPaymentWIthId . Mockery我々の機能を生成するための簡単な方法を提供しており、結果として我々は2回から同じ機能を作成せずに我々の依存関係をテストすることができます、このチュートリアルを支援し、探索を維持!yaを参照してください!
ソース

  • Repo