Golang Stub初体験

8359 ワード

序文


レルムオブジェクトのUTテストでは、インフラストラクチャ層(infra)の操作関数は杭打ちされるべきである.Golangにとって、GoMockは一般的に考えられます.GoMockはGolangの公式開発によって維持されているGolang向けのMockフレームワークで、コードはgithubにある.comで管理します.GoMockは現在、インターネットベースのMock機能を比較的完全に実現しており、Golang内に構築されたtestingパッケージと良好に統合でき、GoMockが作成したテストファイルを使用し、go testコマンドを直接使用してテストすることができ、ユーザーAPIは簡単で友好的である.
GoMockは非常に優れていますが、一般的な関数杭打ちにもいくつかの欠点があります.
  • は、追加の抽象(interface)
  • を導入しなければならない.
  • 杭打ち過程比較重
  • 既存のコードは、新しい抽象
  • に適合しなければならない.
    Golangは閉パケットをサポートしていることを知っています.これにより、関数は別の関数のパラメータまたは戻り値として使用でき、変数に値を割り当てることができます.クローズドパッケージの特性は筆者にStubを思わせ,本稿の体験を開始した.

    Exec関数


    Execはinfra層の操作関数であり、実現は簡単で、コードは以下の通りである.
    func Exec(cmd string, args ...string) (string, error) {
        cmdpath, err := exec.LookPath(cmd)
        if err != nil {
            log.Errorf("exec.LookPath err: %v, cmd: %s", err, cmd)
            return "", infra.ERR_EXEC_LOOKPATH_FAILED
        }
    
        var output []byte
        output, err = exec.Command(cmdpath, args...).CombinedOutput()
        if err != nil {
            log.Errorf("exec.Command.CombinedOutput err: %v, cmd: %s", err, cmd)
            return "", infra.ERR_EXEC_COMBINED_OUTPUT_FAILED
        }
        log.Info("CMD[", cmdpath, "]ARGS[", args, "]OUT[", string(output), "]")
        return string(output), nil
    }
    

    Stub設計


    物理設計


    stubはtestディレクトリの下にあり、*testディレクトリまたはファイルと動作*stub、例えばtest/infra-test/os-encap-test/exec_test.go ==> test/infra-stub/os-encap-stub/exec_stub.go

    既存の関数の再構築


    Exec関数の再構築は非常に簡単です.only and only:
  • 関数の前に「var=」
  • を追加
  • 関数名Execを"="前の
  • に移動
    // infra/os-encap/exec.go
    var Exec = func(cmd string, args ...string) (string, error) {
        cmdpath, err := exec.LookPath(cmd)
        if err != nil {
            log.Errorf("exec.LookPath err: %v, cmd: %s", err, cmd)
            return "", infra.ERR_EXEC_LOOKPATH_FAILED
        }
    
        var output []byte
        output, err = exec.Command(cmdpath, args...).CombinedOutput()
        if err != nil {
            log.Errorf("exec.Command.CombinedOutput err: %v, cmd: %s", err, cmd)
            return "", infra.ERR_EXEC_COMBINED_OUTPUT_FAILED
        }
        log.Info("CMD[", cmdpath, "]ARGS[", args, "]OUT[", string(output), "]")
        return string(output), nil
    }
    

    このような単純な再構成後,Exec関数の呼び出しが一定に保たれるという大きな利点がある.

    Stub関数


    ExecのStub関数はExecInjectと名付けられています.その設計と実現は非常に簡単です.つまり、ExecInject関数には複数のパラメータがあり、戻り値はありません.また、パラメータリストとExec関数の戻り値リストはそれぞれ対応しています.コードは以下の通りです.
    // test/infra-stub/oscap-stub/exec_stub.go
    func ExecInject(output string, err error) {
        osencap.Exec = func(cmd string, args ...string) (string, error) {
            return output, err
        }
    }
    

    Stubシーケンス関数


    同じ関数funcAにM回の最下位動作関数Execが存在し、任意の1回のExec呼び出しでR回再試行できる場合、杭打ちはStubシーケンス関数ExecSeqInjectを使用する必要があり、コードは以下の通りである.
    // test/infra-stub/oscap-stub/exec_stub.go
    func ExecSeqInject(succOutputs []string, tryTimes int, err error) {
        i := 0
        length := 0
        tryFailTimes := 0
        needTry := false
        if tryTimes == 0 {
            length = len(succOutputs)
        } else {
            length = len(succOutputs) - 1
            needTry = true
            tryFailTimes = tryTimes - 1
        }
    
        osencap.Exec = func(cmd string, args ...string) (string, error) {
            if i < length {
                i++
                return succOutputs[i - 1], nil
            }
            if needTry {
                if tryFailTimes > 0 {
                    tryFailTimes--
                    return "", err
                }
            } else {
                return "", err
            }
    
    
            return succOutputs[i], nil
        }
    }
    

    上のコードについて説明します.
  • tryTimes<=0の場合、再試行しないことを示し、通常の呼び出しであり、このテスト例はエラーテストである.succOutputsは、前のlen(succOutputs)次下位操作関数Execが正しく呼び出す戻り値スライス
  • である.
  • tryTimes>0の場合、再試行を表し、再試行の失敗回数はtryTimes-1であり、最後の再試行に成功し、このテストは正しいテストである.succOutputsは、前のlen(succOutputs)-1次下位操作関数Execが正しく呼び出す戻り値と、最後の再試行に成功した戻り値からなるスライス
  • である.
  • 再試行の有無にかかわらず、エラー時の戻り値は(",err)
  • である.
    funcAをテストする時、関数Execに対する杭打ち処理の約束は以下の通りである.
  • 現在のN(0を使用する.
  • 現在のN(0
  • その他の場合は単純なシーンに属し、ExecInjectを直接使用して杭を打つ
  • Stub検証


    4つのUT例を書き、Stubが有効かどうかを検証します.最初の2つの例はStub関数、後の2つの例はStubシーケンス関数で、元の関数のバックアップとリカバリ、すなわちstubの前にバックアップし、テストが完了した後にリカバリする必要があります.
    最初のUT例では、適切な場合にStub関数が正常に注入されたかどうかをテストします.コードは以下の通りです.
    func TestStubDemoForSucc(t *testing.T) {
        backup := osencap.Exec
        defer func() {
            osencap.Exec = backup
        }()
    
        convey.Convey("stub demo for succ
    ", t, func() { outputExpect := "xxx-vethName100-yyy" osencapstub.ExecInject(outputExpect, nil) output, err := osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, nil) convey.So(output, convey.ShouldEqual, outputExpect) }) }

    第2のUT例では、エラーの場合にStub関数が正常に注入されたかどうかをテストし、エラーオブジェクトの個数がStub注入の回数を決定し、コードは以下の通りである.
    const any = "any"
    
    func TestStubDemoForFail(t *testing.T) {
        backup := osencap.Exec
        defer func() {
            osencap.Exec = backup
        }()
    
        convey.Convey("stub demo for fail
    ", t, func() { osencapstub.ExecInject("", infra.ERR_EXEC_LOOKPATH_FAILED) _, err := osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, infra.ERR_EXEC_LOOKPATH_FAILED) osencapstub.ExecInject("", infra.ERR_EXEC_COMBINED_OUTPUT_FAILED) _, err = osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, infra.ERR_EXEC_COMBINED_OUTPUT_FAILED) }) }

    3番目のUT例では、Stubシーケンス関数がエラーのシーンで正常に注入されたかどうかをテストします.コードは次のようになります.
    func TestStubSeqDemoForFailAfter2Succ(t *testing.T) {
        backup := osencap.Exec
        defer func() {
            osencap.Exec = backup
        }()
    
        convey.Convey("stub seq demo for fail after two times succ
    ", t, func() { outputsExpect := []string{"ok", "xxx-vethName100-yyy"} osencapstub.ExecSeqInject(outputsExpect, 0, infra.ERR_EXEC_LOOKPATH_FAILED, ) output, err := osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, nil) convey.So(output, convey.ShouldEqual, outputsExpect[0]) output, err = osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, nil) convey.So(output, convey.ShouldEqual, outputsExpect[1]) _, err = osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, infra.ERR_EXEC_LOOKPATH_FAILED) }) }

    4番目のUT例では、Stubシーケンス関数が正しいシーンで正常に注入されたかどうかをテストします.コードは以下の通りです.
    func TestStubSeqDemoForSuccWithTryAfter2Succ(t *testing.T) {
        backup := osencap.Exec
        defer func() {
            osencap.Exec = backup
        }()
    
        convey.Convey("stub seq demo for succ with try after two times succ
    ", t, func() { outputsExpect := []string{"ok", "xxx-vethName100-yyy", "success"} maxTryTimes := 10 osencapstub.ExecSeqInject(outputsExpect, maxTryTimes, infra.ERR_EXEC_LOOKPATH_FAILED, ) output, err := osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, nil) convey.So(output, convey.ShouldEqual, outputsExpect[0]) output, err = osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, nil) convey.So(output, convey.ShouldEqual, outputsExpect[1]) for i := 0; i < maxTryTimes - 1; i++ { _, err = osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, infra.ERR_EXEC_LOOKPATH_FAILED) } output, err = osencap.Exec(any, any) convey.So(err, convey.ShouldEqual, nil) convey.So(output, convey.ShouldEqual, outputsExpect[2]) }) }

    小結


    Golangにとって、多くの学生はGoMockで杭を打つのが好きです.GoMockは非常に優れていることは否めませんが、下位の操作関数に対してGoMock杭を打つと追加の複雑さが導入されるので、他の方法を試してみたいと思います.本稿では,閉包特性を用いて底層の操作関数を杭打ちし,シーンによって杭打ち関数をStub関数とStubシーケンス関数に分け,簡単で実用的であり,読者に一定の啓発を期待する.記憶とコミュニケーションを容易にするために,筆者はこの方法をGolang Stubと命名した.