十分钟带你快速了解TDD和BDD开发流程

2022-11-14 16:41:07 浏览数 (1)

TDD 有广义和狭义之分,常说的是狭义的 TDD,也就是 UTDD(Unit Test Driven Development)。广义的 TDD 是 ATDD(Acceptance Test Driven Development),包括 BDD(Behavior Driven Development)和 Consumer-Driven Contracts Development 等等。

1. TDD和BDD定义

1.1 TDD(Test-Driven Development)

TDD是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP极限编程(Extreme Programming),同样可以适用于其他开发方法和过程。

TDD首先考虑使用需求(对象、功能、过程、接口等),主要是编写测试用例框架对功能的过程和接口进行设计,而测试框架可以持续进行验证。

1.2 ATDD验收测试驱动(Acceptance Test Driven Development)

ATDD 是 TDD 的延伸。在传统软件设计到交付的做法中,要给系统添加新的特性,开发人员会按照文档开发,测试,最后交给客户验收。ATDD 则有些不同:在编码前先明确新特性的验收标准,将验收标准转换成测试用例(代码),再编写代码让测试通过,当所有的验收条件被满足,也就意味着这个功能完整的实现。

1.3 BDD行为驱动开发(Behavior Driven Development)

BDD是一个设计活动,您可以根据预期行为逐步构建功能块。

使用BDD的团队应该能够以用户故事的形式提供大量的“功能文档”,并增加可执行场景或示例。

TDD、ATDD、BDD对比如下:

对比特性

TDD

BDD

ATDD

定义

TDD是一项开发技术,关注点在功能的实现

BDD是一项开发技术,关注点在系统的行为

ATDD是一项类似BDD的技术,关注点更多是围绕需求

参与者

开发者

开发者、用户、QAs

开发者、用户、QAs

主要关注点

单元测试

理解需求

编写验收测试用例

注意:ATDD与BDD非常相似,它们之间的主要区别是:BDD更多的是聚焦功能点的行为,而ATDD是捕获更精准的需求。

  • BDD重点关注系统的行为,是基于客户角度。
  • ATDD重点是关注系统的实现是否满足要求,是基于QA、产品角度
  • TDD是关注接口方法的测试

他们的关系应该是包含关系的,BDD包含了ATDD,ATDD包含了TDD

2. TDD实施流程

案例参考

(1)重复字符串和冒泡排序:

https://blog.csdn.net/weixin_45960131/article/details/108721261

(2)八皇后:

八皇后场景的TDD实施示例

3. BDD自动化测试

Cucumber 是一个能够理解用普通语言描述的测试用例的支持BDD的自动化测试工具,参考:https://github.com/cucumber/godog

BDD的自动化测试示例如下:

(1)从客户角度编写需求文档,Scenario表示场景,可以多个并行,When、Then、And等关键字描述过程。

代码语言:shell复制
Feature: get version
  In order to know godog version
  As an API user
  I need to be able to request version
 
  Scenario: does not allow POST method
    When I send "POST" request to "/version"
    Then the response code should be 405
    And the response should match json:
      """
      {
        "error": "Method not allowed"
      }
      """
 
  Scenario: should get version number
    When I send "GET" request to "/version"
    Then the response code should be 200
    And the response should match json:
      """
      {
        "version": "v0.0.0-dev"
      }
      """

(2)解析用户文档获取测试用例。

代码语言:go复制
func InitializeScenario(ctx *godog.ScenarioContext) {
    api := &apiFeature{}
 
    ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
        api.resetResponse(sc)
        return ctx, nil
    })
    ctx.Step(`^I send "(GET|POST|PUT|DELETE)" request to "([^"]*)"$`, api.iSendrequestTo)
    ctx.Step(`^the response code should be (d )$`, api.theResponseCodeShouldBe)
    ctx.Step(`^the response should match json:$`, api.theResponseShouldMatchJSON)
}

(3)编写测试方法。

代码语言:go复制
func (a *apiFeature) iSendrequestTo(method, endpoint string) (err error) {
    req, err := http.NewRequest(method, endpoint, nil)
    if err != nil {
        return
    }
 
    // handle panic
    defer func() {
        switch t := recover().(type) {
        case string:
            err = fmt.Errorf(t)
        case error:
            err = t
        }
    }()
 
    switch endpoint {
    case "/version":
        getVersion(a.resp, req)
    default:
        err = fmt.Errorf("unknown endpoint: %s", endpoint)
    }
    return
}
 
func (a *apiFeature) theResponseCodeShouldBe(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
}
 
func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) (err error) {
    var expected, actual interface{}
 
    // re-encode expected response
    if err = json.Unmarshal([]byte(body.Content), &expected); err != nil {
        return
    }
 
    // re-encode actual response too
    if err = json.Unmarshal(a.resp.Body.Bytes(), &actual); err != nil {
        return
    }
 
    // the matching may be adapted per different requirements.
    if !reflect.DeepEqual(expected, actual) {
        return fmt.Errorf("expected JSON does not match actual, %v vs. %v", expected, actual)
    }
    return nil
}

(4)编写业务代码,让测试代码通过。

代码语言:go复制
func getVersion(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        fail(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    data := struct {
        Version string `json:"version"`
    }{Version: godog.Version}
 
    ok(w, data)
}

(5)BDD同时支持对数据库的测试,可以根据需求文档中的描述自动建库和建表,并插入测试数据。

代码语言:shell复制
Feature: users
  In order to use users api
  As an API user
  I need to be able to manage users
 
  Scenario: should get empty users
    When I send "GET" request to "/users"
    Then the response code should be 200
    And the response should match json:
      """
      {
        "users": []
      }
      """
 
  Scenario: should get users
    Given there are users:
      | username | email             |
      | john     | john.doe@mail.com |
      | jane     | jane.doe@mail.com |
    When I send "GET" request to "/users"
    Then the response code should be 200
    And the response should match json:
      """
      {
        "users": [
          {
            "username": "john"
          },
          {
            "username": "jane"
          }
        ]
      }
      """
 
  Scenario: should get users when there is only one
    Given there are users:
      | username | email           |
      | gopher   | gopher@mail.com |
    When I send "GET" request to "/users"
    Then the response code should be 200
    And the response should match json:
      """
      {
        "users": [
          {
            "username": "gopher"
          }
        ]
      }
      """

(6)运行测试。

代码语言:shell复制
go test

至此,一个完整的BDD测试就完成了。

0 人点赞