优美的测试代码 - 行为驱动开发(BDD)

可理解的代码非常重要,测试代码也是如此。在我看来,优秀的测试代码,必须做到一个重要的事情就是保持测试逻辑的清晰。一个完整的测试案例通常包括三个部分:
1. SetUp
2. Exercise
3. Verifiy
4. TearDown
一 个测试案例如果能清晰的区分这三个部分,其实已经成功了一半。但是,如果仅仅只是做到这一步,离我们的“可理解的”测试代码还有些距离。在我看来,要做到 测试代码的可理解,首先要做到的是使用简洁清晰的方式表达每个测试案例的测试过程。我们都不希望在一个测试案例中看到一个long long method,因为我们需要去理解,你调用了那么多方法,做了那么多处理,到底是在干啥?当然,我们也不希望看到一个极端情况:一个测试案例中就看到一个 函数调用。这样做确实做到了测试案例表面上的简洁,但是,却失去了表现内部细节的机会。我们需要表述的是测试过程和方法,比如,进行了什么样的操作,输入 了什么样的数据,然后得出了什么样的结果,和预期结果对比怎么样等。如果把这些都封装到一个方法,别人只能理解你可能会做什么,但很难去理解你将如何去 做。其次,在测试案例fail的时候,能够清晰准确的定位问题。极限情况下,一个测试案例里应该只能有一个断言,或者说一个检查点。测试粒度的缩小能够加 快问题的定位。通常,当一个测试案例fail的时候,我们还需要定位到底是测试代码的问题还是被测代码的问题。这是一个烦人的过程,定位问题的人不一定就 是写这段测试代码的人,就算是写这段测试代码的人,也需要时间去理解测试案例干了什么。这时,测试过程及方法的清晰表述就变得尤为重要。
有的人会 想到通过文档来表述测试过程和方法,在我看来,这不是一个好办法。敏捷开发中的一个思想:“一个过时的文档比没有文档更加糟糕”。为什么需要文档才能表述清楚你的测试过程? 只能说明你的测试代码足够糟糕,无法让人理解。一个糟糕的代码就算当初写下了足够详细的说明文档,随着时间的交替,人员的变更,代码的修改维护,复杂的文 档和代码会变得越来越不同步。导致的结果将会是代码越来越糟糕复杂,文档越来越过时。最好的文档,其实是代码。
我们先来看一下一个老外写的完整的测试案例代码:
@Test
public void shouldBeAbleToEditAPage() {
    Given.thatThe(wiki).wasAbleTo(beAtThe(PointWhereItHasBeen.JUST_INSTALLED));
    And.thatThe(user).wasAbleTo(navigateToTheHomePage());

    When.the(user).attemptsTo(changeTheContent().to(
"Welcome to Acceptance Test Driven Development"));

    Then.the(textOnTheScreen().ofThe(user)).shouldBe(
"Welcome to Acceptance Test Driven Development");
}

我 之前并没有为这个测试案例做任何说明,相信每个看过这个测试案例的人都能非常容易的理解这个测试案例的行为,甚至是对代码不通的人。因为上面的测试代码使 用了几乎是自然语言的方式,描述了其测试的过程。了解的人一定看出来了,其实,这就是行为驱动开发,Behavior Driven Development,简称:BDD。


行为驱动开发(Behavior Driven Development)

Behaviour-Driven Development (BDD) is an evolution in the thinking behind TestDrivenDevelopment and AcceptanceTestDrivenPlanning.

It brings together strands from TestDrivenDevelopment and DomainDrivenDesign into an integrated whole, making the relationship between these two powerful approaches to software development more evident.

It aims to help focus development on the delivery of prioritised, verifiable business value by providing a common vocabulary (also referred to as a UbiquitousLanguage) that spans the divide between Business and Technology.
BDD 使用几乎近于自然语言的方式描述了软件的行为过程,因此,可以直接作为软件的需求文档,也可以直接应用到测试中,作为测试的标准文档。我们在做单元测试 时,经常是针对某个函数,或是某个类进行测试,但是被测函数或是被测的类是可能经常变化的,我们的测试案例也需要经常性的随之变化。然后,BDD描述的是软件的整个系统行为,几近于需求文档,可变性大大减小。因此,测试案例不需要做太大变化。同时,这样的测试案例最贴近于需求,贴近于实际的系统行为。
BDD描述的行为就像一个个的故事(Story),系统业务专家、开发者、测试人员一起合作,分析软件的需求,然后将这些需求写成一个个的故事。开发者负责填充这些故事的内容,测试者负责检验这些故事的结果。通常,会使用一个故事的模板来对故事进行描述:
As a [X]
I want [Y]
so that [Z]

同样的一个故事,可能会有不同的场景。通过上面的模板描述了故事之后,再通过下面的模板对不同场景进行描述:
Given some initial context (the givens),
When an event occurs,
then ensure some outcomes.

一个经典的例子就是ATM取款机的例子。故事的描述为:
Title: Customer withdraws cash
As a customer,
I want to withdraw cash from an ATM,
so that I don’t have to waiin line at the bank.

作为一个客户,我去ATM取钱,就不需要去排队。同样的故事,会有不同的场景发生:
Scenario 1: Account is in credit
Given the account is in credit
And the card is valid
And the dispenser contains cash
When the customer requests cash
Then ensure the account is debited
And ensure cash is dispensed
And ensure the card is returned

如果你取款的金额比你的存款还多,将是下面的场景:
Scenario 2: Account is overdrawn past the overdraft limit
Given the account is overdrawn
And the card is valid
When the customer requests cash
Then ensure a rejection message is displayed
And ensure cash is not dispensed
And ensure the card is returned

有了这样的故事、场景的描述,测试者可以通过一些BDD的测试框架将上面的故事转成测试代码(当然,也可直接由开发来完成,这里说测试者是为了方便理解),开发者实现产品代码,并保证测试代码通过。


常见的BDD框架


回到正题

刚才就像对一门新的技术或方向进行了了解,再回过头来想想,BDD对于测试的意义到底在哪。通过上面的了解,我们知道了行为驱动开发并不止是一个测试行为,其实很大意义上是一个产品需求分析人员、开发、测试共同来完成的一个行为。BDD同时也是一个非常理想化的过程,几乎现在大部分的公司连TDD都实现不 了。作为测试人员,我们不是要去抱怨,我们应该庆幸作为测试开发人员,我们有机会使用最前沿,最先进的技术去解决问题。开发不能做到TDD,我们可以对自己的测试代码实施TDD,我们可以尝试各种不同的语言,然后选择最优雅的方式去实现。同样的,我们可以使用BDD所使用的自然语言描述方法来编写我们的测试案例。这其实才是我本文的关键所在。
使用行为驱动开发,还需要打破传统的魄力。因为之前几乎没有人会告诉你一个函数的命名为ShouldXXX(),也不会有有When, Then,And之类的类或函数。当你习惯它,会变得非常好玩。
使用行为驱动开发,可以使我们的案例描述逻辑更加清晰,可以想象以后维护你的代码的人会对你大赞一番,因为省去了他理解代码的大部分时间。
使用行为驱动开发,可以建立自动化测试与手工系统测试的桥梁。或许可以形成这样的模式:手工测试人员负责设计故事和场景,自动化测试人员负责实现故事和场景。通过这样的联系,手工测试人员能够更好的了解到自动化测试所做的内容,从而免去不必要的重复劳动。
使用行为驱动开发,可以使你的测试更加贴近实际的用户行为,从而找到系统的问题所在。


没有银弹

没有银弹》(No Silver Bullet)是IBM大型电脑之父佛瑞德·布鲁克斯(Fred Brooks)在1987年所发表的一篇关于软件工程的 经典论文。该论述中强调由于软件的复杂性本质,而使真正的银弹并不存在;所谓的没有银弹是指没有任何一项技术或方法可使软件工程的生产力在十年内提高十 倍。刚才提到的使用行为驱动开发的种种好处,但是,如果运用不当,往往会适得其反。我们使用When,Then之类的连词作为函数,并不是说可以随意根据 需要随意的命名。比如,使用This或These作为函数名,其行为就应该是返回一个或多个对象,而不是做其他的事情。可以想象,如果一个函数的名称是 These,里面执行的确实一个文件拷贝的操作,对于代码的维护者来说是多么糟糕的一件事情啊。
其实我对行为驱动开发也只是初略的了解,本文表达的是仅仅是我个人的一些思想,如有错误之处也希望大家能够指出。最后,我希望达到的是:

优美的测试代码,就是一个个优美的故事。

本文参考资料:
http://behaviour-driven.org/
http://dannorth.net/introducing-bdd
http://www.javaworld.com/javaworld/jw-09-2008/jw-09-easyb.html
posted @ 2009-07-26 23:29  CoderZh  阅读(15763)  评论(7编辑  收藏  举报