一、前言
本人負責的支付清結算方向的測試工作,在測試專案中,會出現流程化的介面呼叫,請求完一個介面後,繼續請求另一個介面(這裡的介面可以指Http,也指rpc介面),這裡以一個真實場景為例:使用者在平臺下單,結算前部分退款,再結算,最後結算後部分退款;
第一個介面動作對應使用者下單,第二個動作對應結算前的部分退款,第三個動作對應結算,第四個動作對應結算後的部分退款,涉及不同系統的互動;這是一個完整的場景,根據我們的測試用例與更多的使用者場景,實際測試中,我們需要測試更多場景,單介面測試已無法滿足實際需求。
二、表格驅動測試
我們可以定義一個結構體,將每一個步驟定義成一個節點,通過遍歷節點達到執行整個流程的效果:
優點就是程式碼更加清晰、明確,也便於調整步驟的順序、新增或者移除某些步驟。另外,在迴圈體中增加除錯日誌也非常的簡單;
但還是有缺點的,看上去似乎不滿足介面測試一些要求,沒有case管理,無法做介面斷言等
func main() { ctx := &context{} steps := []struct { name string fn func() error }{ {"parse flags", ctx.parseFlags}, {"read schema", ctx.readSchema}, {"dump schema", ctx.dumpSchema}, // Before transformations {"remove builtin constructors", ctx.removeBuiltinConstructors}, {"add adhoc constructors", ctx.addAdhocConstructors}, {"validate schema", ctx.validateSchema}, {"decompose arrays", ctx.decomposeArrays}, {"replace arrays", ctx.replaceArrays}, {"resolve generics", ctx.resolveGenerics}, {"dump schema", ctx.dumpSchema}, // After transformations {"decode combinators", ctx.decodeCombinators}, {"dump decoded combinators", ctx.dumpDecodedCombinators}, {"codegen", ctx.codegen}, } for _, step := range steps { ctx.debugf("start %s step", step.name) if err := step.fn(); err != nil { log.Fatalf("%s: %v", step.name, err) } } }
三、封裝
將場景和節點定義成結構體,提供場景與節點獨立的執行介面:
實際的節點,需要定義成這個結構體的方法;
type SenceSuite struct { SenceSuite string } type Plan struct { Planname string Fn func(map[string]interface{}) interface{} Data map[string]interface{} } var SenceSuiteDao *SenceSuite var SenceSuiteOnce sync.Once func NewSenceSuiteDao() *SenceSuite { SenceSuiteOnce.Do( func() { SenceSuiteDao = &SenceSuite{} }) return SenceSuiteDao } func (dao *SenceSuite) DoSence(steps []Plan) { for _, step := range steps { step.Fn(step.Data) } } func (dao *SenceSuite) DoPlan(step Plan) interface{} { return step.Fn(step.Data) }
四、實際使用
介面case管理:"github.com/smartystreets/goconvey/convey"
這裡仍然以上面的場景為例:
//結算前部分退款,再結算,部分退款 func TestRefundAndNomalSettleAndRefund(t *testing.T) { // 初始化資料庫 utils.DBInit() //使用者下單 order := GetOrder("ZFB", "SELF", "nrmol") env := "prod" way := "1" SenceSuite := utils.NewSenceSuiteDao() convey.Convey("結算前部分退款", t, func() { P1 := utils.Plan{Planname: "結算前部分退款", Fn: SenceSuite.Refund, Data: map[string]interface{}{}} res := SenceSuite.DoPlan(P1).(*xxx) convey.So(res.RetCode, convey.ShouldEqual, "000000") }) convey.Convey("正常結算", t, func() { P2 := utils.Plan{Planname: "正常結算", Fn: SenceSuite.Settle, Data: map[string]interface{}{}} res := SenceSuite.DoPlan(P2).(*xxx) convey.So(res.RetCode, convey.ShouldEqual, "000000") }) convey.Convey("結算後部分退款", t, func() { P3 := utils.Plan{Planname: "結算後部分退款", Fn: SenceSuite.Refund, Data: map[string]interface{}{}} res := SenceSuite.DoPlan(P3).(*xxx) convey.So(res.RetCode, convey.ShouldEqual, "000000") }) }