行为驱动开发
行为驱动开发(Behaviour-Driven-Development)简写BDD
BDD是TDD的一种演化,作为一种设计方法,可以有效的改善设计,并在系统演化过程中未团队知名前进方向
行为驱动开发的根基是一种“通用语言”。这种通用语言同时被客户和开发者用来定义系统的行为。由于客户
和开发者使用同一种语言来描述同一个系统, 可以最大程度避免表达不一致带来的问题。
书写格式:
Story:标题(秒速故事的单行文字)
As a 角色
I want 特征
So that 利益
(用一系列的场景来定义验证标准)
Scenario 1 :标题(秒速场景的单行文字)
Give [上下文】
And [更多上下文】
When [事件】
Then [结果】
清单 2. 用于探索行为的一个简单的栈定义
值调用 push(),那么栈应该 抛出一个异常。现在看看我在清单 3 中如何定义这个行为。
清单 3. 如果推出一个 null 值,则栈应该抛出一个异常
在清单 3 中发生的一些事情是 JBhave 特有的,所以要解释一下。首先,我创建 Stack 类的一个实例,并将它限制为 String 类型(通过 Java 5 泛型)。接下来,我使用 JBehave 的 异常框架 实际建模我所期望的行为。 Ensure 类类似于 JUnit 或 TestNG 的 Assert 类型;但是,它增加了一系列方法,提供了更具可读性的 API(这常被称作文学编程)。在清单 3 中,我确保了如果对 null 调用 push(),则抛出一个RuntimeException。
JBehave 还引入了一个 Block 类型,它是通过用所需的行为覆盖 run() 方法来实现的。在内部,JBehave 确保期望的异常类型不被抛出(并因此被捕捉),而是生成一个故障状态。您可能还记得,在我前面关于 用 Google Web Toolkit 对 Ajax 进行单元测试 的文章中,也出现了类似的覆盖便利类的模式。在那种情况下,覆盖是通过 GWT 的 Timer 类实现的。
如果现在运行清单 3 中的行为,应该看到出现错误。按照目前编写的代码,push() 方法不执行任何操作。所以不可能生成异常,从清单 4 中的输出可以看到这一点。
清单 4. 没有发生期望的行为
清单 4 中的句子 “StackBehavior should throw exception upon null push” 模拟行为的名称(shouldThrowExceptionUponNullPush()),并加上类的名称。 实际上,JBehave 是在报告当它运行所需的行为时,没有获得任何反应。当然,我的下一步是要使上述行为成功运行,为此我检查 null,如清单 5 所示。
清单 5. 在栈类中增加指定的行为
当我重新运行行为时,一切都运行得很好,如清单 6 所示。
清单 6 中的输出与 JUnit 的输出是不是很像?这也许不是巧合,对不对?如前所述,JBehave 是根据 xUnit 范例建模的,它甚至通过 setUp() 和tearDown() 提供了对 fixture 的支持。由于我可能在整个行为类中使用一个 Stack 实例,我可能也会将那种逻辑推入(这里并非有意使用双关语)到一个 fixture 中,正如清单 7 中那样。注意, JBehave 将与 JUnit 一样遵循相同的 fixture 规则 — 也就是说,对于每个行为方法,它都运行一个 setUp() 和 tearDown()。
清单 7. JBehave 中的 fixture
清单 8. 确保 pop 的行为
确保行为
从技术上讲,在这里我可以将 pop() 实现为无论调用顺序如何,都只抛出一个异常。但是当我沿着这条行为路线前进时,我又忍不住考虑一个支持我所需要的规范的实现。在这种情况下,如果 push() 没有被调用(或者从逻辑上讲,栈为空)的情况下确保 pop() 抛出一个异常,则意味着栈有一个状态。正如之前 Linda 思考的那样,栈通常有一个 “内部容器”,用于实际持有项目。相应地,我可以为 Stack 类创建一个ArrayList,用于保持传递给 push() 方法的值,如清单 9 所示。
清单 9. 栈需要一种内部的方式来持有对象
现在我可以为 pop() 方法编写行为,即确保当栈在逻辑上为空时,抛出一个异常。
清单 10. pop 的实现变得更容易
接下来的行为方法是 shouldPopPushedValue(),这个行为方法很容易指定。我只是 push() 一个值(“test”),并确保当调用 pop() 时,返回相同的值。
清单 11. 如果将一个值入栈,那么出栈的也应该是它,对吗?
为 Matcher 挑选 ‘M’
在清单 11 中,我确保 pop() 返回值 “test”。在使用 JBehave 的 Ensure 类的过程中,您常常会发现,需要一种更丰富的方式来表达期望。JBehave 提供了一种 Matcher 类型用于实现丰富的期望,从而满足了这一需求。而我选择重用 JBehave 的 UsingMatchers 类型(清单 11 中的 m 变量),所以可以使用 is()、and()、or() 等方法和很多其它整洁的机制来构建更具文学性的期望。
清单 11 中的 m 变量是 StackBehavior 类的一个静态成员,如清单 12 所示。
清单 12. 行为类中的 UsingMatchers
private static final UsingMatchers m = new UsingMatchers(){};
有了清单 11 中编写的新的行为方法之后,现在可以来运行它 — 但是这时会产生一个错误,如清单 13 所示。
清单 13. 新编写的行为不能运行
Failures: 1.
1) StackBehavior should pop pushed value:
java.lang.RuntimeException: nothing to pop
怎么回事?原来是我的 push() 方法还没有完工。回到 清单 5,我编写了一个最简单的实现,以使我的行为可以运行。现在是时候完成这项工作了,即真正将被推入的值添加到内部容器中(如果这个值不为 null)。如清单 14 所示。
清单 14. 完成 push 方法
清单 15. JBehave 报告一个 null 值,而不是一个异常
清单 16. 是时候编写完这个 pop 方法了
1) StackBehavior should pop pushed value:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
仔细阅读清单 17 中的实现可以发现问题:在处理 ArrayList 时,我需要考虑 0。
清单 18. 通过考虑 0 修复问题
栈的逻辑
至此,通过允许传递多个行为方法,我已经实现了 push() 和 pop() 方法。但是我还没有处理栈的实际内容,这是与多个 push() 和 pop() 相关联的逻辑,间或出现一个 peek()。
首先,我将通过 shouldPopSecondPushedValueFirst() 行为确保栈的基本算法(先进先出)无误。
清单 19. 确保典型的栈逻辑
清单 19 中的代码可以按计划运行,所以我将实现另一个行为方法(在清单 20 中),以确保两次使用 pop() 都能表现出正确的行为。
清单 20. 更深入地查看栈行为
由于 peek() 还没有定义,因此清单 21 还不能编译。在清单 22 中,我定义了 peek() 的一个最简单的实现。
清单 22. 当前,peek 是必需的
public E peek() {
return null;
}
现在 StackBehavior 类可以编译,但是它仍然不能运行。
清单 23. 返回 null 并不奇怪,对吗?
1) StackBehavior should leave value on stack after peep:
VerificationException: Expected:
same instance as <test 2>
but got:
null:
在逻辑上,peek() 不会从内部集合中移除 项目,它只是传递指向那个项目的指针。因此,我将对 ArrayList 使用 get() 方法,而不是remove() 方法,如清单 24 所示。
清单 24. 不要移除它
public E peek() {
return this.list.get(this.list.size()-1);
}
栈为空的情况
现在重新运行 清单 21 中的行为,结果顺利通过。但是,在这样做的过程中发现一个问题:如果栈为空,则 peek() 有怎样的行为?如果说栈为空时调用 pop() 会抛出一个异常,那么 peek() 是否也应该如此?
Linda 对此没有进行解释,所以,显然我需要自己添加新的行为。在清单 25 中,我为 “当之前没有调用 push() 时调用 peek() 会怎样” 这个场景编写了代码。
清单 25. 如果没有调用 push 就调用 peek,会怎样?
public void shouldReturnNullOnPeekWithoutPush() throws Exception{
Ensure.that(stStack.peek(), m.is(null));
}
同样,不会感到意外。如清单 26 所示,问题出现了。
清单 26. 没有可执行的内容
1) StackBehavior should return null on peek without push:
java.lang.ArrayIndexOutOfBoundsException: -1
修复这个缺陷的逻辑类似于 pop() 的逻辑,如清单 27 所示。
清单 27. 这个 peek() 需要做一些修复
把我对 Stack 类作出的所有修改和修复综合起来,可以得到清单 28 中的代码。
在此,StackBehavior 类运行 7 种行为,以确保 Stack 类能按照 Linda 的(和我自己的一点)规范运行。Stack 类 还可能使用某种重构(也许pop() 方法 应该调用 peek() 进行测试,而不是执行 size() 检查?),但是由于一直使用了行为驱动过程,我可以很自信地对代码作出更改。如果出现了问题,很快就可以收到通知。
结束语
您可能已经注意到,本月对行为驱动开发(BDD)的探索中,Linda 实际上就是客户。在这里,可以把 Frank 看作开发人员。如果把这里的领域(即数据结构)换成其它领域(例如一个呼叫中心应用程序),以上应用仍然类似。作为客户或领域专家的 Linda 指出系统、特性或应用程序应该 执行什么功能,像 Frank 这样的开发人员则使用 BDD 确保正确理解了她的要求并实现这些需求。
对于很多开发人员来说,从测试驱动开发转移到 BDD 是明智的转变。 如果采用 BDD,就不必考虑测试,而只需注意应用程序的需求,并确保应用程序的行为执行它 应该 执行的功能,以满足那些需求。
在这个例子中,使用 BDD 和 JBehave 使我可以根据 Linda 的说明轻松地实现一个可正常工作的栈。通过首先 考虑行为,我只需倾听她的需求,然后相应地构建栈。在此过程中,我还发现了 Linda 没有提及的关于栈的其他内容。
参考资料
学习
您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
“追求代码质量:对 Ajax 应用程序进行单元测试”(Andrew Glover,developerWorks,2007 年 7 月):通过使用 GWT 和它的重写类 Timer,测试 Ajax 应用程序变得更容易。
“追求代码质量: 使用 Selenium 和 TestNG 进行编程式测试”(Andrew Glover, developerWorks,2007 年 4 月):学习如何使用 TestNG 作为测试驱动器,通过编程的方式运行 Selenium 测试。
“使用 RSpec 进行行为驱动测试”(Bruce Tate,developerWorks,2007 年 8 月):在过去一年里,测试领域中最为瞩目的创新应属 RSpec 的引入和快速发展,它是一种行为驱动测试工具。了解 RSpec 如何改变人们思考测试的方式。
“Introducing BDD”(Dan North, DanNorth.net,2006 年 9 月):了解 Dan North 如何将 BDD 作为一种实践。
“Using BDD to drive development”(Andrew Glover,testearly.com,2007 年 7 月):Andrew 再次介绍 BDD 如何驱动开发,同样也是基于 JBehave。
“Mocks are hip when it comes to BDD”(Andrew Glover,thediscoblog.com,2007 年 7 月):Andrew 通过 JBehave 的 mocking 库重新发现 mock 对象,然后他使用这种对象驱动快速开发。
追求代码质量系列(Andrew Glover,developerWorks):学习更多关于编写专注于质量的代码的信息。
developerWorks Java 技术专区:这里有数百篇关于 Java 编程方方面面的文章。
获得产品和技术
下载 JBehave:面向 Java 平台的完全启用的BDD框架。
讨论
参与论坛讨论。
Discussion forum: Improve your code quality: 向代码质量完美主义者学习!作为一名专注于提高代码质量的顾问,Andrew Glover 分享了这方面的专业知识。
行为驱动开发(Behaviour-Driven-Development)简写BDD
BDD是TDD的一种演化,作为一种设计方法,可以有效的改善设计,并在系统演化过程中未团队知名前进方向
行为驱动开发的根基是一种“通用语言”。这种通用语言同时被客户和开发者用来定义系统的行为。由于客户
和开发者使用同一种语言来描述同一个系统, 可以最大程度避免表达不一致带来的问题。
书写格式:
Story:标题(秒速故事的单行文字)
As a 角色
I want 特征
So that 利益
(用一系列的场景来定义验证标准)
Scenario 1 :标题(秒速场景的单行文字)
Give [上下文】
And [更多上下文】
When [事件】
Then [结果】
And [其他结果】
JBehave
JBehave是用于java平台的一个BDD框架,源于xUnit范例,JBehave强调“应该”这个词,而不是测试,和JUnit一样,
你可以在自己喜欢的IDE中,或者偏爱的构建平台(例如Ant)运行JBehave类
JBehave允许以JUnit的方式创建行为类,但是,在JBehave中,不需要扩展任何特定的基类,并且所有行为方法都需
要以should而不是test开发
清单 1.用于栈的一个简单行为类
public class StackBehavior {
public void shouldThrowExceptionUponNullPush() throws Exception{}
public void shouldThrowExceptionUponPopWithoutPush() throws Exception{}
public void shouldPopPushedValue() throws Exception{}
public void shouldPopSecondPushedValueFirst() throws Exception{}
public void shouldLeaveValueOnStackAfterPeep() throws Exception{}
}
清单 1中定义的方法都是以should开头,他们都创建一个类可读的句子,这里产生的StackBehavior类描述栈的特征。
清单 2. 用于探索行为的一个简单的栈定义
public class Stack<E> {
public void push(E value) {}
}
可以看到,我编写了一个最简单的栈,以便首先 添加必需的行为。正如 Linda 所说,行为很简单:如果有人对 null
值调用 push(),那么栈应该 抛出一个异常。现在看看我在清单 3 中如何定义这个行为。
清单 3. 如果推出一个 null 值,则栈应该抛出一个异常
public void shouldThrowExceptionUponNullPush() throws Exception{
final Stack<String> stStack = new Stack<String>();
Ensure.throwsException(RuntimeException.class, new Block(){
public void run() throws Exception {
stStack.push(null);
}
});
}
杰出的 expectation 和 override
在清单 3 中发生的一些事情是 JBhave 特有的,所以要解释一下。首先,我创建 Stack 类的一个实例,并将它限制为 String 类型(通过 Java 5 泛型)。接下来,我使用 JBehave 的 异常框架 实际建模我所期望的行为。 Ensure 类类似于 JUnit 或 TestNG 的 Assert 类型;但是,它增加了一系列方法,提供了更具可读性的 API(这常被称作文学编程)。在清单 3 中,我确保了如果对 null 调用 push(),则抛出一个RuntimeException。
JBehave 还引入了一个 Block 类型,它是通过用所需的行为覆盖 run() 方法来实现的。在内部,JBehave 确保期望的异常类型不被抛出(并因此被捕捉),而是生成一个故障状态。您可能还记得,在我前面关于 用 Google Web Toolkit 对 Ajax 进行单元测试 的文章中,也出现了类似的覆盖便利类的模式。在那种情况下,覆盖是通过 GWT 的 Timer 类实现的。
如果现在运行清单 3 中的行为,应该看到出现错误。按照目前编写的代码,push() 方法不执行任何操作。所以不可能生成异常,从清单 4 中的输出可以看到这一点。
清单 4. 没有发生期望的行为
1) StackBehavior should throw exception upon null push:
VerificationException: Expected:
object not null
but got:
null:
清单 4 中的句子 “StackBehavior should throw exception upon null push” 模拟行为的名称(shouldThrowExceptionUponNullPush()),并加上类的名称。 实际上,JBehave 是在报告当它运行所需的行为时,没有获得任何反应。当然,我的下一步是要使上述行为成功运行,为此我检查 null,如清单 5 所示。
清单 5. 在栈类中增加指定的行为
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}
}
当我重新运行行为时,一切都运行得很好,如清单 6 所示。
清单 6. 成功!
Time: 0.021s
Total: 1. Success!
行为驱动开发
清单 6 中的输出与 JUnit 的输出是不是很像?这也许不是巧合,对不对?如前所述,JBehave 是根据 xUnit 范例建模的,它甚至通过 setUp() 和tearDown() 提供了对 fixture 的支持。由于我可能在整个行为类中使用一个 Stack 实例,我可能也会将那种逻辑推入(这里并非有意使用双关语)到一个 fixture 中,正如清单 7 中那样。注意, JBehave 将与 JUnit 一样遵循相同的 fixture 规则 — 也就是说,对于每个行为方法,它都运行一个 setUp() 和 tearDown()。
清单 7. JBehave 中的 fixture
public class StackBehavior {
private Stack<String> stStack;
public void setUp() {
this.stStack = new Stack<String>();
}
//...
}
对于接下来的行为方法,shouldThrowExceptionUponPopWithoutPush() 表示我必须确保它具有类似于 清单 3 中的shouldThrowExceptionUponNullPush() 的行为。从清单 8 中可以看出,没有任何特别神奇的地方 — 有吗?
清单 8. 确保 pop 的行为
public void shouldThrowExceptionUponPopWithoutPush() throws Exception{
Ensure.throwsException(RuntimeException.class, new Block() {
public void run() throws Exception {
stStack.pop();
}
});
}
您可能已经清楚地知道,此时清单 8 并不会真正地编译,因为 pop() 还没有被编写。但是,在开始编写 pop() 之前,让我们考虑一些事情。
确保行为
从技术上讲,在这里我可以将 pop() 实现为无论调用顺序如何,都只抛出一个异常。但是当我沿着这条行为路线前进时,我又忍不住考虑一个支持我所需要的规范的实现。在这种情况下,如果 push() 没有被调用(或者从逻辑上讲,栈为空)的情况下确保 pop() 抛出一个异常,则意味着栈有一个状态。正如之前 Linda 思考的那样,栈通常有一个 “内部容器”,用于实际持有项目。相应地,我可以为 Stack 类创建一个ArrayList,用于保持传递给 push() 方法的值,如清单 9 所示。
清单 9. 栈需要一种内部的方式来持有对象
public class Stack<E> {
private ArrayList<E> list;
public Stack() {
this.list = new ArrayList<E>();
}
//...
}
现在我可以为 pop() 方法编写行为,即确保当栈在逻辑上为空时,抛出一个异常。
清单 10. pop 的实现变得更容易
public E pop() {
if(this.list.size() > 0){
return null;
}else{
throw new RuntimeException("nothing to pop");
}
}
当我运行清单 8 中的行为时,一切如预期运行:由于栈中没有存在任何值(因此它的大小不大于 0),于是抛出一个异常。
接下来的行为方法是 shouldPopPushedValue(),这个行为方法很容易指定。我只是 push() 一个值(“test”),并确保当调用 pop() 时,返回相同的值。
清单 11. 如果将一个值入栈,那么出栈的也应该是它,对吗?
public void shouldPopPushedValue() throws Exception{
stStack.push("test");
Ensure.that(stStack.pop(), m.is("test"));
}
为 Matcher 挑选 ‘M’
在清单 11 中,我确保 pop() 返回值 “test”。在使用 JBehave 的 Ensure 类的过程中,您常常会发现,需要一种更丰富的方式来表达期望。JBehave 提供了一种 Matcher 类型用于实现丰富的期望,从而满足了这一需求。而我选择重用 JBehave 的 UsingMatchers 类型(清单 11 中的 m 变量),所以可以使用 is()、and()、or() 等方法和很多其它整洁的机制来构建更具文学性的期望。
清单 11 中的 m 变量是 StackBehavior 类的一个静态成员,如清单 12 所示。
清单 12. 行为类中的 UsingMatchers
private static final UsingMatchers m = new UsingMatchers(){};
有了清单 11 中编写的新的行为方法之后,现在可以来运行它 — 但是这时会产生一个错误,如清单 13 所示。
清单 13. 新编写的行为不能运行
Failures: 1.
1) StackBehavior should pop pushed value:
java.lang.RuntimeException: nothing to pop
怎么回事?原来是我的 push() 方法还没有完工。回到 清单 5,我编写了一个最简单的实现,以使我的行为可以运行。现在是时候完成这项工作了,即真正将被推入的值添加到内部容器中(如果这个值不为 null)。如清单 14 所示。
清单 14. 完成 push 方法
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}else{
this.list.add(value);
}
}
但是,等一下 — 当我重新运行该行为时,它仍然失败!
清单 15. JBehave 报告一个 null 值,而不是一个异常
1) StackBehavior should pop pushed value:
VerificationException: Expected:
same instance as <test>
but got:
null:
至少清单 15 中的失败有别于清单 13 中的失败。在这种情况下,不是抛出一个异常,而是没有发现 "test" 值;实际弹出的是 null。仔细观察清单 10 会发现:一开始我将 pop() 方法编写为当内部容器中有项目时,就返回 null。问题很容易修复。
清单 16. 是时候编写完这个 pop 方法了
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size());
}else{
throw new RuntimeException("nothing to pop");
}
}
清单 17. 另一个错误
1) StackBehavior should pop pushed value:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
仔细阅读清单 17 中的实现可以发现问题:在处理 ArrayList 时,我需要考虑 0。
清单 18. 通过考虑 0 修复问题
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size()-1);
}else{
throw new RuntimeException("Nothing to pop");
}
}
栈的逻辑
至此,通过允许传递多个行为方法,我已经实现了 push() 和 pop() 方法。但是我还没有处理栈的实际内容,这是与多个 push() 和 pop() 相关联的逻辑,间或出现一个 peek()。
首先,我将通过 shouldPopSecondPushedValueFirst() 行为确保栈的基本算法(先进先出)无误。
清单 19. 确保典型的栈逻辑
public void shouldPopSecondPushedValueFirst() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
}
清单 19 中的代码可以按计划运行,所以我将实现另一个行为方法(在清单 20 中),以确保两次使用 pop() 都能表现出正确的行为。
清单 20. 更深入地查看栈行为
public void shouldPopValuesInReverseOrder() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 1"));
}
接下来,我要确保 peek() 能按预期运行。正如 Linda 所说,peek() 遵从和 pop() 相同的规则,但是 “应该保留栈顶的项目”。相应地,我在清单 21 中实现了 shouldLeaveValueOnStackAfterPeep() 方法的行为。
清单 21. 确保 peek 保留栈顶的项目
public void shouldLeaveValueOnStackAfterPeep() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.peek(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 2"));
}
由于 peek() 还没有定义,因此清单 21 还不能编译。在清单 22 中,我定义了 peek() 的一个最简单的实现。
清单 22. 当前,peek 是必需的
public E peek() {
return null;
}
现在 StackBehavior 类可以编译,但是它仍然不能运行。
清单 23. 返回 null 并不奇怪,对吗?
1) StackBehavior should leave value on stack after peep:
VerificationException: Expected:
same instance as <test 2>
but got:
null:
在逻辑上,peek() 不会从内部集合中移除 项目,它只是传递指向那个项目的指针。因此,我将对 ArrayList 使用 get() 方法,而不是remove() 方法,如清单 24 所示。
清单 24. 不要移除它
public E peek() {
return this.list.get(this.list.size()-1);
}
栈为空的情况
现在重新运行 清单 21 中的行为,结果顺利通过。但是,在这样做的过程中发现一个问题:如果栈为空,则 peek() 有怎样的行为?如果说栈为空时调用 pop() 会抛出一个异常,那么 peek() 是否也应该如此?
Linda 对此没有进行解释,所以,显然我需要自己添加新的行为。在清单 25 中,我为 “当之前没有调用 push() 时调用 peek() 会怎样” 这个场景编写了代码。
清单 25. 如果没有调用 push 就调用 peek,会怎样?
public void shouldReturnNullOnPeekWithoutPush() throws Exception{
Ensure.that(stStack.peek(), m.is(null));
}
同样,不会感到意外。如清单 26 所示,问题出现了。
清单 26. 没有可执行的内容
1) StackBehavior should return null on peek without push:
java.lang.ArrayIndexOutOfBoundsException: -1
修复这个缺陷的逻辑类似于 pop() 的逻辑,如清单 27 所示。
清单 27. 这个 peek() 需要做一些修复
public E peek() {
if(this.list.size() > 0){
return this.list.get(this.list.size()-1);
}else{
return null;
}
}
把我对 Stack 类作出的所有修改和修复综合起来,可以得到清单 28 中的代码。
清单 28. 一个可正常工作的栈
import java.util.ArrayList;
public class Stack<E> {
private ArrayList<E> list;
public Stack() {
this.list = new ArrayList<E>();
}
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}else{
this.list.add(value);
}
}
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size()-1);
}else{
throw new RuntimeException("Nothing to pop");
}
}
public E peek() {
if(this.list.size() > 0){
return this.list.get(this.list.size()-1);
}else{
return null;
}
}
}
在此,StackBehavior 类运行 7 种行为,以确保 Stack 类能按照 Linda 的(和我自己的一点)规范运行。Stack 类 还可能使用某种重构(也许pop() 方法 应该调用 peek() 进行测试,而不是执行 size() 检查?),但是由于一直使用了行为驱动过程,我可以很自信地对代码作出更改。如果出现了问题,很快就可以收到通知。
结束语
您可能已经注意到,本月对行为驱动开发(BDD)的探索中,Linda 实际上就是客户。在这里,可以把 Frank 看作开发人员。如果把这里的领域(即数据结构)换成其它领域(例如一个呼叫中心应用程序),以上应用仍然类似。作为客户或领域专家的 Linda 指出系统、特性或应用程序应该 执行什么功能,像 Frank 这样的开发人员则使用 BDD 确保正确理解了她的要求并实现这些需求。
对于很多开发人员来说,从测试驱动开发转移到 BDD 是明智的转变。 如果采用 BDD,就不必考虑测试,而只需注意应用程序的需求,并确保应用程序的行为执行它 应该 执行的功能,以满足那些需求。
在这个例子中,使用 BDD 和 JBehave 使我可以根据 Linda 的说明轻松地实现一个可正常工作的栈。通过首先 考虑行为,我只需倾听她的需求,然后相应地构建栈。在此过程中,我还发现了 Linda 没有提及的关于栈的其他内容。
参考资料
学习
您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
“追求代码质量:对 Ajax 应用程序进行单元测试”(Andrew Glover,developerWorks,2007 年 7 月):通过使用 GWT 和它的重写类 Timer,测试 Ajax 应用程序变得更容易。
“追求代码质量: 使用 Selenium 和 TestNG 进行编程式测试”(Andrew Glover, developerWorks,2007 年 4 月):学习如何使用 TestNG 作为测试驱动器,通过编程的方式运行 Selenium 测试。
“使用 RSpec 进行行为驱动测试”(Bruce Tate,developerWorks,2007 年 8 月):在过去一年里,测试领域中最为瞩目的创新应属 RSpec 的引入和快速发展,它是一种行为驱动测试工具。了解 RSpec 如何改变人们思考测试的方式。
“Introducing BDD”(Dan North, DanNorth.net,2006 年 9 月):了解 Dan North 如何将 BDD 作为一种实践。
“Using BDD to drive development”(Andrew Glover,testearly.com,2007 年 7 月):Andrew 再次介绍 BDD 如何驱动开发,同样也是基于 JBehave。
“Mocks are hip when it comes to BDD”(Andrew Glover,thediscoblog.com,2007 年 7 月):Andrew 通过 JBehave 的 mocking 库重新发现 mock 对象,然后他使用这种对象驱动快速开发。
追求代码质量系列(Andrew Glover,developerWorks):学习更多关于编写专注于质量的代码的信息。
developerWorks Java 技术专区:这里有数百篇关于 Java 编程方方面面的文章。
获得产品和技术
下载 JBehave:面向 Java 平台的完全启用的BDD框架。
讨论
参与论坛讨论。
Discussion forum: Improve your code quality: 向代码质量完美主义者学习!作为一名专注于提高代码质量的顾问,Andrew Glover 分享了这方面的专业知识。