Golang Stub初体験
8359 ワード
序文
レルムオブジェクトのUTテストでは、インフラストラクチャ層(infra)の操作関数は杭打ちされるべきである.Golangにとって、GoMockは一般的に考えられます.GoMockはGolangの公式開発によって維持されているGolang向けのMockフレームワークで、コードはgithubにある.comで管理します.GoMockは現在、インターネットベースのMock機能を比較的完全に実現しており、Golang内に構築されたtestingパッケージと良好に統合でき、GoMockが作成したテストファイルを使用し、go testコマンドを直接使用してテストすることができ、ユーザーAPIは簡単で友好的である.
GoMockは非常に優れていますが、一般的な関数杭打ちにもいくつかの欠点があります.
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:
// 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
}
}
上のコードについて説明します.
funcAをテストする時、関数Execに対する杭打ち処理の約束は以下の通りである.
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と命名した.