研發同窗完成功能開發後,通常經過單元測試或手動測試,來驗證本身寫的功能是否正確運行。 可是這些測試不少是從開發角度出發,存在樣例單1、測試覆蓋率不全、非研發同窗沒法較全面瞭解產品的行爲表現等狀況。html
<!--more-->
近幾年 BDD 做爲一種流行的測試方式和產品驗收手段,能較好地解決如下兩個問題:git
基於上面兩點,本文介紹了團隊在 Go 項目開發過程當中接入 BDD 的一個實踐,以及一些感悟體會。github
BDD 會在 PRD Review 時開始介入,產品經理在給出產品需求文檔的同時,會提供具體的業務場景(features),完成開發後,BDD 測試會做爲驗收工做的一部分,測試流程以下:正則表達式
BDD 風格的 Go 測試框架主流有3個:json
這些框架都有本身的一些特性:segmentfault
咱們的對框架選擇有兩點考慮:後端
go test
。由於 GoDog 支持 Gherkin 語法,容易上手, 咱們最後選擇了 GoDog。api
以以前開發的項目爲例, setting.feature 文件以下:app
Feature: Search Bar Setting Add a search bar's setting. Scenario: Create a search bar. When I send "POST" request to "/settings" with request body: """ { "app": { "key": "automizely", "platform": "shopify" }, "organization": { "id": "a66827cd0c72190e0036166003617822" }, "enabled": true } """ Then I expect that the response code should be 201 And the response should match json: """ { "meta": { "code": 20100, "type": "Created", "message": "The request was successful, we created a new resource and the response body contains the representation." }, "data": { "id": "2b736ff9914143338e00e46c97e3948f", "app": { "platform": "shopify", "key": "automizely" }, "organization": { "id": "a66827cd0c72190e0036166003617822" }, "enabled": true, "created_at": "2020-03-04T07:00:04+00:00", "updated_at": "2020-03-04T07:00:04+00:00" } } """
這是一個具體的後端業務場景:經過 POST 方法發起新建setting請求。HTTP Code返回201,返回的 Response 與給出的樣例 JSON 匹配,知足以上兩點,BDD 纔會測試經過。框架
go get github.com/cucumber/godog/cmd/godog@v0.8.1
godog 可生成feature文件對應的測試代碼模板。終端執行 godog features/email.feature
,生成模板代碼:
// You can implement step definitions for undefined steps with these snippets: func iSendRequestToWithRequestBody(arg1, arg2 string, arg3 *messages.PickleStepArgument_PickleDocString) error { return godog.ErrPending } func iExpectThatTheResponseCodeShouldBe(arg1 int) error { return godog.ErrPending } func theResponseShouldMatchJson(arg1 *messages.PickleStepArgument_PickleDocString) error { return godog.ErrPending } func FeatureContext(s *godog.Suite) { s.Step(`^I send "([^"]*)" request to "([^"]*)" with request body:$`, iSendRequestToWithRequestBody) s.Step(`^I expect that the response code should be (\d+)$`, iExpectThatTheResponseCodeShouldBe) s.Step(`^the response should match json:$`, theResponseShouldMatchJson) }
將代碼拷貝到 setting_test.go,開始補充每一步要執行的動做。
godog 定義 Suite 結構體,經過註冊函數來執行每一個 Gherkin 文本表達式。FeatureContext 至關於測試的入口,能夠作一些前置和後置 hook。Suite 會以正則表達式匹配的方式,執行每一個匹配到的動做。
func FeatureContext(s *godog.Suite) { api := &apiFeature{} s.BeforeSuite(InitBDDEnv) s.BeforeScenario(api.resetResponse) s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, api.iSendRequestTo) s.Step(`^I expect that the response code should be (\d+)$`, api.iExpectThatTheResponseCodeShouldBe) s.Step(`^I send "([^"]*)" request to "([^"]*)" with request body:$`, api.iSendRequestToWithRequestBody) s.Step(`^the response should match json:$`, api.theResponseShouldMatchJson) s.AfterSuite(appctx.CloseMockSpannerAndClients) }
BeforSuite 是前置 hook,用於一些服務配置。在這個項目裏,咱們調用 InitBDDEnv 函數, 初始化 application:加載配置、初始化各個組件和生成 ApiRouter:
func InitBDDEnv() { // Load test config config, err := conf.LoadConfig() if err != nil { return } appctx.InitMockApplication(config) // Create Table and import data in fake db PrepareMockData() // Start a mock API Server server = apiserver.NewApiServer(apiserver.ServerConfig{ Port: 8080, // can modify BasePath: "/businesses/v1", }) server.AddApiGroup(BuildApiGroup(context.Background(), config)) }
發起 API 請求:
func (a *apiFeature) iSendRequestToWithRequestBody(method, url string, body *messages.PickleStepArgument_PickleDocString) error { var payload []byte var data interface{} // Unmarshal body.Content and get correct payload if err := json.Unmarshal([]byte(body.Content), &data); err != nil { return err } var err error if payload, err = json.Marshal(data); err != nil { return err } req, err := http.NewRequest(method, url, bytes.NewReader(payload)) if err != nil { return err } // filling result to httpRecorder server.GinEngine().ServeHTTP(a.resp, req) return nil }
對請求響應的校驗:
func (a *apiFeature) iExpectThatTheResponseCodeShouldBe(code int) error { if code != a.resp.Code { return fmt.Errorf("expected response code to be: %d, but actual is: %d", code, a.resp.Code) } return nil }
godog features/setting.feature
就能夠跑 BDD 了。目前業界主流的開發模式有 TDD、BDD 和 DDD, 實際的項目中,由於面對各類不一樣需求和其餘因素,決定了咱們所採用的開發模式。本文介紹了 BDD 開發模式的實踐,是咱們團隊在 Go 項目接入 BDD 的第一次探索,實際應用效果良好,有效解決了開發和產品之間溝通和協做的痛點問題;以及做爲 TDD 的一種補充,咱們試圖轉換一種觀念,經過業務行爲,約束它應該如何運行,而後抽象出能達成共識的規範,來保證根據設計所編寫的測試,就是用戶指望的功能。