研发同窗完成功能开发后,通常经过单元测试或手动测试,来验证本身写的功能是否正确运行。 可是这些测试不少是从开发角度出发,存在样例单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 的一种补充,咱们试图转换一种观念,经过业务行为,约束它应该如何运行,而后抽象出能达成共识的规范,来保证根据设计所编写的测试,就是用户指望的功能。