乐趣区

关于golang:Go-项目中的-BDD-实践

背景

研发同学实现性能开发后,个别通过单元测试或手动测试,来验证本人写的性能是否正确运行。然而这些测试很多是从开发角度登程,存在样例繁多、测试覆盖率不全、非研发同学无奈较全面理解产品的行为表现等状况。

<!–more–>
近几年 BDD 作为一种风行的测试形式和产品验收伎俩,能较好地解决以下两个问题:

  1. 缩小开发和产品的沟通老本,减少合作。比方产品经理通过 feature 文件的形式,更具体地给开发者阐明想要预期成果。
  2. 综合测试。BDD 可能把上线之后手工测试这一过程自动化。

基于下面两点,本文介绍了团队在 Go 我的项目开发过程中接入 BDD 的一个实际,以及一些感悟领会。

BDD 流程

BDD 会在 PRD Review 时开始染指,产品经理在给出产品需要文档的同时,会提供具体的业务场景 (features),实现开发后,BDD 测试会作为验收工作的一部分,测试流程如下:

  1. PO 事后提供 BDD 测试样例的 feature 文件。
  2. 后端实现性能开发后,编写 feature 样例对应的测试代码。
  3. 实现 BDD 测试编码,本地测试通过后提交代码,发动 Pull Request。
  4. CI 主动触发 BDD 测试,在测试通过后,才能够 Merge Pull Request。

测试框架

BDD 格调的 Go 测试框架支流有 3 个:

  1. Ginkgo
  2. GoConvey
  3. GoDog

这些框架都有本人的一些个性:

  • Ginkgo 和 GoConvey 反对 BDD 格调的解析语法、展现测试的覆盖率的性能。
  • GoConvey 有 Web UI 界面,用户体验好。
  • GoDog 的定位是反对行为驱动框架 Cucumber。

咱们的对框架抉择有两点思考:

  1. 反对 Gherkin 语法,不须要太高的学习老本,产品和研发能合作。
  2. 间接集成到 go test

因为 GoDog 反对 Gherkin 语法,容易上手, 咱们最初抉择了 GoDog。

BDD 实际

以之前开发的我的项目为例, setting.feature 文件如下:

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. 装置 godog:go 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
退出移动版