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测试就完成了。