首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

亲身体验行为驱动开发(2)

亲身体验行为驱动开发(2)

杰出的 expectation 和 override在清单 3 中发生的一些事情是 JBhave 特有的,所以要解释一下。首先,我创建 Stack 类的一个实例,并将它限制为 String 类型(通过 Java 5 泛型)。接下来,我使用 JBehave 的异常框架 实际建模我所期望的行为。Ensure 类类似于 JUnit 或 TestNG 的Assert 类型;但是,它增加了一系列方法,提供了更具可读性的 API(这常被称作文学编程)。在清单 3 中,我确保了如果对 null 调用 push(),则抛出一个 RuntimeException。
JBehave 还引入了一个 Block 类型,它是通过用所需的行为覆盖 run() 方法来实现的。在内部,JBehave 确保期望的异常类型不被抛出(并因此被捕捉),而是生成一个故障状态。您可能还记得,在我前面关于  的文章中,也出现了类似的覆盖便利类的模式。在那种情况下,覆盖是通过 GWT 的 Timer 类实现的。
如果现在运行清单 3 中的行为,应该看到出现错误。按照目前编写的代码,push() 方法不执行任何操作。所以不可能生成异常,从清单 4 中的输出可以看到这一点。
清单 4. 没有发生期望的行为
1
2
3
4
5
1) StackBehavior should throw exception upon null push:
VerificationException: Expected:
object not null
but got:
null:




清单 4 中的句子 “StackBehavior should throw exceptionupon null push” 模拟行为的名称(shouldThrowExceptionUponNullPush()),并加上类的名称。  实际上,JBehave 是在报告当它运行所需的行为时,没有获得任何反应。当然,我的下一步是要使上述行为成功运行,为此我检查 null,如清单 5 所示。
清单 5. 在栈类中增加指定的行为
1
2
3
4
5
public void push(E value) {
  if(value == null){
   throw new RuntimeException("Can't push null");
  }
}




当我重新运行行为时,一切都运行得很好,如清单 6 所示。
清单 6. 成功!
1
2
3
Time: 0.021s

Total: 1. Success!




行为驱动开发清单 6 中的输出与 JUnit 的输出是不是很像?这也许不是巧合,对不对?如前所述,JBehave 是根据 xUnit 范例建模的,它甚至通过 setUp() 和 tearDown() 提供了对 fixture 的支持。由于我可能在整个行为类中使用一个 Stack 实例,我可能也会将那种逻辑推入(这里并非有意使用双关语)到一个 fixture 中,正如清单 7 中那样。注意,  JBehave 将与 JUnit 一样遵循相同的 fixture 规则 — 也就是说,对于每个行为方法,它都运行一个 setUp() 和 tearDown()。
清单 7. JBehave 中的 fixture
1
2
3
4
5
6
7
8
public class StackBehavior {
private Stack<String> stStack;
   
public void setUp() {
  this.stStack = new Stack<String>();
}
//...
}




对于接下来的行为方法,shouldThrowExceptionUponPopWithoutPush() 表示我必须确保它具有类似于  中的 shouldThrowExceptionUponNullPush() 的行为。从清单 8 中可以看出,没有任何特别神奇的地方  — 有吗?
清单 8. 确保 pop 的行为
1
2
3
4
5
6
7
8
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. 栈需要一种内部的方式来持有对象
1
2
3
4
5
6
7
8
public class Stack<E> {
private ArrayList<E> list;

public Stack() {
  this.list = new ArrayList<E>();
}
//...
}




现在我可以为 pop() 方法编写行为,即确保当栈在逻辑上为空时,抛出一个异常。
清单 10. pop 的实现变得更容易
1
2
3
4
5
6
7
public E pop() {
if(this.list.size() > 0){
  return null;
}else{
  throw new RuntimeException("nothing to pop");
}
}




当我运行 中的行为时,一切如预期运行:由于栈中没有存在任何值(因此它的大小不大于 0),于是抛出一个异常。
接下来的行为方法是 shouldPopPushedValue(),这个行为方法很容易指定。我只是 push() 一个值(“test”),并确保当调用 pop() 时,返回相同的值。
清单 11. 如果将一个值入栈,那么出栈的也应该是它,对吗?
1
2
3
4
public void shouldPopPushedValue() throws Exception{
stStack.push("test");
Ensure.that(stStack.pop(), m.is("test"));
}

返回列表