Go 項目中的 BDD 實踐

title

背景

研發同窗完成功能開發後,通常經過單元測試或手動測試,來驗證本身寫的功能是否正確運行。 可是這些測試不少是從開發角度出發,存在樣例單1、測試覆蓋率不全、非研發同窗沒法較全面瞭解產品的行爲表現等狀況。html

<!--more-->
近幾年 BDD 做爲一種流行的測試方式和產品驗收手段,能較好地解決如下兩個問題:git

  1. 減小開發和產品的溝通成本,增長協做。好比產品經理經過 feature文件的方式,更具體地給開發者說明想要預期效果。
  2. 綜合測試。 BDD 可以把上線以後手工測試這一過程自動化。

基於上面兩點,本文介紹了團隊在 Go 項目開發過程當中接入 BDD 的一個實踐,以及一些感悟體會。github

BDD流程

BDD 會在 PRD Review 時開始介入,產品經理在給出產品需求文檔的同時,會提供具體的業務場景(features),完成開發後,BDD 測試會做爲驗收工做的一部分,測試流程以下:正則表達式

  1. PO 預先提供 BDD 測試樣例的 feature 文件。
  2. 後端完成功能開發後,編寫 feature 樣例對應的測試代碼。
  3. 完成 BDD 測試編碼,本地測試經過後提交代碼,發起 Pull Request。
  4. CI 自動觸發 BDD 測試,在測試經過後,才能夠 Merge Pull Request。

    10fe77dbc44c7a9ed9cac290fdf64200.png

測試框架

BDD 風格的 Go 測試框架主流有3個:json

  1. Ginkgo
  2. GoConvey
  3. GoDog

這些框架都有本身的一些特性:segmentfault

  • Ginkgo 和 GoConvey 支持 BDD 風格的解析語法、展現測試的覆蓋率的功能。
  • GoConvey 有 Web UI 界面,用戶體驗好。
  • GoDog 的定位是支持行爲驅動框架 Cucumber。

咱們的對框架選擇有兩點考慮:後端

  1. 支持 Gherkin 語法,不須要過高的學習成本,產品和研發能協做。
  2. 直接集成到go test

由於 GoDog 支持 Gherkin 語法,容易上手, 咱們最後選擇了 GoDog。api

BDD實踐

以以前開發的項目爲例, 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 纔會測試經過。框架

下面是經過 GoDog 來完成這個場景的測試:
  1. 安裝 godoggo get github.com/cucumber/godog/cmd/godog@v0.8.1
  2. 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,開始補充每一步要執行的動做。

  3. 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)
    }
  4. 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))
       }
  5. 發起 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
    }
  6. 對請求響應的校驗:

    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
    }
  7. 完成測試文件的編寫後, 執行 godog features/setting.feature 就能夠跑 BDD 了。

總結

目前業界主流的開發模式有 TDD、BDD 和 DDD, 實際的項目中,由於面對各類不一樣需求和其餘因素,決定了咱們所採用的開發模式。本文介紹了 BDD 開發模式的實踐,是咱們團隊在 Go 項目接入 BDD 的第一次探索,實際應用效果良好,有效解決了開發和產品之間溝通和協做的痛點問題;以及做爲 TDD 的一種補充,咱們試圖轉換一種觀念,經過業務行爲,約束它應該如何運行,而後抽象出能達成共識的規範,來保證根據設計所編寫的測試,就是用戶指望的功能。

參考

  1. what is BDD
  2. go-and-test-cucumber
  3. How to Use Godog for Behavior-driven Development in Go
  4. gherkin-test-cucumber
  5. 關於TDD和BDD
相關文章
相關標籤/搜索