TDD是一个迭代开发的过程,每个迭代开始于写一个要形成我们正在实现的规格的一部分的测试。短迭代让我们得到对正在写的代码的更多的即时反馈,糟糕的设计决策更容易被捕捉。对任何产品代码都优先写测试,良好的单元测试伴随良好的范围覆盖,但那只是它的一个副作用。
每隔几个星期,通过该网站的访问历史,我们重新审视我们的读者最喜欢的帖子。这个指南首次发布于2010年11月。
颠倒开发顺序
在传统的编程中,直到概念完全在代码中体现,问题被编程解决。理想状态,代码遵照一些完整的架构设计思考,尽管在很多情况下,可能不是这种情况,特别在JavaScript的世界。这种编程风格通过猜测需要什么代码解决问题来解决问题,这种策略很容易导致臃肿而紧耦合的方案。如果没有单元测试,这种方法生成的代码甚至可能会产生永远都不会执行的代码,如错误处理逻辑和“柔性”参数处理,或包含没有彻底测试或根本没有测试的边界检查情况。
测试驱动开发颠倒了开发周期。不再是关注于什么需要代码来解决问题,测试驱动开发通敲定目标开始。关于什么行动受支持并解释,单元测试形成规格和文档。但是,TDD的目标是测试,所以它不能保证它对例如边界情况处理的更好。但是,每一行代码都被示例代码的典型片断测试过,TDD可能会减少产生多余的代码,而对应的功能可能更强健。完全的测试驱动开发保证系统不会包含执行不到的代码。
过程
驱动测试开发过程是一个迭代的过程,每个迭代包括下面四个步骤:
· 写一个测试
· 运行测试,看到新测试失败
· 使该测试通过
· 重构以清除重复
在每个迭代,没有都是规格。一旦已经写了足够的产品代码(并且没有更多)使测试通过,我们就完工了,我们就应该重构代码以消除重复和/或改善设计,同时保持测试仍然通过。
TDD实践:观察者模式
观察者模式(也叫出版/订阅模式或简称pubsub)是一个设计模式,它允许我们观察一个对象的状态,并当它发生改变时会被通知。该模式可以提供一个具有强扩展点的对象同时维持松耦合。
观察者模式有两个角色—被观察者和观察者。观察者是一个对象或函数,它在被观察者状态改变时会被通知。被观察者决定什么时间通知和提供什么数据给它的观察者。被观察者至少提供两个公共方法:通知他的观察者有新数据的pubsub,和订阅观察者事件的pubsub。
被观察者库
测试驱动开发允许我们在需要时以非常小的步子前进。在这第一个现实世界的例子中,我们以最微小的步子开始。随着我们对代码和过程信心的增强,当环境允许时(即实现很不重要的代码),我们将逐步的增加步子的大小。以小的频繁迭代写代码能帮助我们设计的API更好,帮助我们犯更小的错误。当出错时,我们能很快的修复它们,因为错误很容易在每次我们添加一些代码后执行测试时跟踪到。
搭建环境
本例使用JsTestDriver运行测试。官方网站上有环境搭建指南。
像下面这样初始化项目布局:
1. chris@laptop:~/projects/observable $ tree
2. .
3. |-- jsTestDriver.conf
4. |-- src
5. | `-- observable.js
6. `-- test
7. `-- observable_test.js
最小的JsTestDriver配置文件:
1. server: http://localhost:4224
2. load:
3. - lib/*.js
4. - test/*.js
添加观察者
我们首先开始有实现一个计划作为观察者的对象的想法。要这样做会让我们首先写一个测试,并看着它失败,通过最直接的方式让它通过,然后重构让它更合理。
第一个测试
第一个测试将添加一个叫addObserver 的方法的观察者。为了验证这个工作,我们假定被观察者把观察者保存到一个数组中,然后验证观察者数组中只有一项。测试在test/observable_test.js中,其内容如下面所示:
TestCase("ObservableAddObserverTest", { "test should store function": function () { var observable = new tddjs.Observable(); var observer = function () {}; observable.addObserver(observer); assertEquals(observer, observable.observers[0]); } });
|
执行测试并看着它失败:
乍一看,第一个测试的执行结果是毁灭性的:
Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) Firefox 3.6.12 Linux: Run 1 tests (Passed: 0; Fails: 0; Errors 1) (0.00 ms) ObservableAddObserverTest.test should store function error (0.00 ms): \ tddjs is not defined /test/observable_test.js:3 Tests failed.
|
使测试通过
不要害怕!失败其实是个好事:它告诉我们努力的目标。第一个严重的问题是由于tddjs不存在。我们在src/observable.js中添加命名空间对象:
重新运行测试产生一个新错误:
1. E 2. Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) 3. Firefox 3.6.12 Linux: Run 1 tests (Passed: 0; Fails: 0; Errors 1) (0.00 ms) 4. ObservableAddObserverTest.test should store function error (0.00 ms): \ 5. tddjs.Observable is not a constructor 6. /test/observable_test.js:3 7. Tests failed.
|
我们通过添加一个空的Observable构造器来修正这个新问题:
1. var tddjs = {}; 2. (function () { 3. function Observable() {} 4. tddjs.Observable = Observable; 5. }());
|
再一次执行测试立即又带来了下一个问题:
1. E 2. Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) 3. Firefox 3.6.12 Linux: Run 1 tests (Passed: 0; Fails: 0; Errors 1) (0.00 ms) 4. ObservableAddObserverTest.test should store function error (0.00 ms): \ 5. observable.addObserver is not a function 6. /test/observable_test.js:6 7. Tests failed.
|
添加缺失的方法:
1. function addObserver() {} 2. Observable.prototype.addObserver = addObserver;
|
随着方法的添加,现在测试提示缺少观察者数组。
1. E 2. Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) 3. Firefox 3.6.12 Linux: Run 1 tests (Passed: 0; Fails: 0; Errors 1) (0.00 ms) 4. ObservableAddObserverTest.test should store function error (0.00 ms): \ 5. observable.observers is undefined 6. /test/observable_test.js:8 7. Tests failed.
|
看起来有点奇怪,我现在要在pubsub方法中定义观察者数组。当一个测试时,TDD教导我们做能工作的最简单的事情,别管它有多糟糕。一旦测试通过,我们有机会审视我们的工作。
1. function addObserver(observer) { 2. this.observers = [observer]; 3. } 4. Success! The test now passes: 5. . 6. Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (1.00 ms) 7. Firefox 3.6.12 Linux: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1.00 ms)
|
重构
在开发出现在的解决方案的时候,我们采取了最快的路径来通过测试。现在条形是绿色的,我们可以审视解决方案并执行任何我们认为需要的重构。在这最后一步,惟一的规则就是保持条形是绿色的。这意味着我们不得不以微小的步子重构,确保不会意外破坏任何事情。
目前的实现有两个问题要处理。测试对被观察者的实现做了详细的假设(Thetest makes detailed
assumptions about the implementation of Observable)和addObserver
实现是对测试的硬编码。
我们首先处理硬编码。为了暴露硬编码方案,我们增加测试使它添加两个观察者而不是一个。
1. "test should store function": function () { 2. var observable = new tddjs.Observable(); 3. var observers = [function () {}, function () {}]; 4. observable.addObserver(observers[0]); 5. observable.addObserver(observers[1]); 6. assertEquals(observers, observable.observers); 7. }
|
正如预期,测试失败了。测试期望函数能添加观察者并堆积,像任何元素添加到一个pubsub一样。为了实现这个功能,我们把数组实例到构造函数中,并简单的使用数组方法push代理addObserver
:
1. function Observable() { 2. this.observers = []; 3. } 4. function addObserver(observer) { 5. this.observers.push(observer); 6. }
|
有了这个实现,测试又一次通过了,验证我们已经处理了硬编码方案。然而,存取一个公共属性的问题和野蛮的假设被观察者的实现也是一个问题。一个可观察的pubsub应该是对任意数量的对象可观察的,但外部对象对如何存储它们或把它们存到哪里不感兴趣。理想情况,如果某一观察者已注册,我们希望能不需要搜索被观察者的内部就能用它检查。我们做了一个注解并继续前行。随后,我们回来改进这个测试。
检查观察者
我们给被观察者添加另一个方法,hasObserver,并用它除去部分当实现addObserver时引起的混乱。
测试
一个新方法开始于一个新测试,下一个测试期望hasObserver 方法的行为:
1. TestCase("ObservableHasObserverTest", { 2. "test should return true when has observer": function () { 3. var observable = new tddjs.Observable(); 4. var observer = function () {}; 5. observable.addObserver(observer); 6. assertTrue(observable.hasObserver(observer)); 7. } 8. }); |
我们期望这个测试失败于缺少hasObserver,实际上确实如此。
使测试通过
又一次,我们采用能使测试通过的最简单的方案。
1. function hasObserver(observer) { 2. return true; 3. } 4. Observable.prototype.hasObserver = hasObserver;
|
虽然从长远看我们知道这不能解决我们的问题,但它保持测试是绿色的。尝试审视和重构让我们无从下手,因为没有明显可以改善的点。测试就是我们的需求,当前它们只需要hasObserver
返回true。为了修正它,我们加入另一个对不存在的观察者期望hasObserver 返回false的测试,它可以帮助促使真实的方案。
1. "test should return false when no observers": function () { 2. var observable = new tddjs.Observable(); 3. assertFalse(observable.hasObserver(function () {})); 4. }
|
这个测试可悲的失败了,因为hasObserver 总是返回true,强迫我们来产生真实的方案。如果一个观察者已经注册,验证就是个简单的检查this.observers数组包含起初传过来的对象事情。
1. function hasObserver(observer) { 2. return this.observers.indexOf(observer) >= 0; 3. }
|
如果元素不在数组中,Array.prototype.indexOf方法返回一个小于0的数值,所以验证它返回的数值大于等于0就会告诉我们观察者是否存在。
解决浏览器不兼容
在多于一个浏览器中运行测试会产生令人吃惊的结果:
1. chris@laptop:~/projects/observable$ jstestdriver --tests all 2. ...E 3. Total 4 tests (Passed: 3; Fails: 0; Errors: 1) (11.00 ms) 4. Firefox 3.6.12 Linux: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (2.00 ms) 5. Microsoft Internet Explorer 6.0 Windows: Run 2 tests \ 6. (Passed: 1; Fails: 0; Errors 1) (0.00 ms) 7. ObservableHasObserverTest.test should return true when has observer error \ 8. (0.00 ms): Object doesn't support this property or method 9. Tests failed.
|
IE版本6和7测试失败,用最通用的错误信息提示:“对象不支持此属性或方法”。这可以指任意数量的问题:
· 我们调用了一个空对象的方法
· 我们调用了一个不存在的方法
· 我们操作了一个不存在的属性
幸运的是,TDD步子微小,我们知道错误和最近添加的对观察者数组的indexOf 调用有关。事实证明,IE6和7不支持JavaScript1.6的Array.prototype.indexOf方法(对此,我们真不能责怪它,它只是最近在2009年12月的ECMAScript
5被标准化)。关于这一点,我们有三个选择:
· 避免在hasObserver中使用Array.prototype.indexOf,重复使用浏览器支持的原生功能。
· 为不支持的浏览器实现数组Array.prototype.indexOf。或者选择实现一个提供相同功能的帮助函数
· 使用提供缺失方法或相似方法的第三方库
哪一个方法最适合解决给定问题要视情况而定:它们都有自己的优点缺点。如果更看中保持被观察者的自包含,我们会使用循环代替indexOf简单实现hasObserver,有效的解决问题。同时,这好像也是让它能正常工作的最简单的事情。如果以后遇到同样的相似的情况,我们会被建议重新考虑我们的决定。更新的
hasObserver像这样:
1. function hasObserver(observer) { 2. for (var i = 0, l = this.observers.length; i < l; i++) { 3. if (this.observers[i] == observer) { 4. return true; 5. } 6. } 7. return false; 8. }
|
重构
随着条形回到绿色,是时候该审视我们进展了。我们现在有三个测试,但其中的两个出奇的相似。我们写的第一个验证addObserver
的正确性测试基本上和为验证重构写的测试做了相同的事。在这两个测试中有两个关键的不同:第一个测试预先被声明为发臭的,因为它直接存取被观察者对象内部观察者数组。第二个测试添加两个观察者,确保它们都被添加了。我们现在把两个测试连接两个测试为一个验证所有添加到被观察者的观察者是真实的添加了。
view plaincopy to clipboardprint? 1. "test should store functions": function () { 2. var observable = new tddjs.Observable(); 3. var observers = [function () {}, function () {}]; 4. observable.addObserver(observers[0]); 5. observable.addObserver(observers[1]); 6. assertTrue(observable.hasObserver(observers[0])); 7. assertTrue(observable.hasObserver(observers[1])); 8. }
|
通知观察者
添加观察者并验证他们存在已经正常,但没有能力通知他们感兴趣的变化,被观察者就没有什么用。是实现通知方法的时候了。
保证观察者被调用
通知执行最重要的任务是调用观察者。为达到这个目的,我们需要一些方法验证在通知之后观察者的确被调用了。为了验证一个函数被调用,我们可以在这个函数被调用时设置一个属性。为验证测试,我们可以检查属性是否被设置。下面的测试在第一个notify测试中使用这个思想。
1. TestCase("ObservableNotifyTest", { 2. "test should call all observers": function () { 3. var observable = new tddjs.Observable(); 4. var observer1 = function () { observer1.called = true; }; 5. var observer2 = function () { observer2.called = true; }; 6. observable.addObserver(observer1); 7. observable.addObserver(observer2); 8. observable.notify(); 9. assertTrue(observer1.called); 10. assertTrue(observer2.called); 11. } 12. });
|
为了使这个测试通过我们需要循环观察者数组并调用每个函数。
1. function notify() { 2. for (var i = 0, l = this.observers.length; i < l; i++) { 3. this.observers[i](); 4. } 5. } 6. Observable.prototype.notify = notify;
|
传参数
现在观察者已经被调用了,但它们没有被送入任何数据。它们知道什么事情发生了-但不需要知道是什么。我们使通知带任意数量的参数,简单的把他们传递给每个观察者:
1. "test should pass through arguments": function () { 2. var observable = new tddjs.Observable(); 3. var actual; 4. observable.addObserver(function () { 5. actual = arguments; 6. }); 7. observable.notify("String", 1, 32); 8. assertEquals(["String", 1, 32], actual); 9. }
|
测试通过把接收的参数赋值给一个本地变量来对比接收的参数和传递的参数。我们创建的观察者实际是一个非常简单的人工测试间谍。运行测试确认它是失败的,这并不令人吃惊,因为我们现在还没有在notify中接触参数。
为了使测试通过,当调用观察者时,我们可以使用apply:
1. function notify() { 2. for (var i = 0, l = this.observers.length; i < l; i++) { 3. this.observers[i].apply(this, arguments); 4. } 5. }
|
简单的修改后,测试条又回到了绿色。注意我们传递this作为apply的第一个参数,意思是观察者使用被观察者作为this被调用。
错误处理
这个时候被观察者是功能的并且我们已经测试验证了它的行为。然而,测试只验证了被观察者响应期望输入正确的行为。如果有人尝试注册一个对象作为观察者而不是一个函数会发生什么事呢?如果其中一个观察者挂掉了会发生什么事呢?这些都是需要测试来回答的问题。在预期的情况下确保正确的行为是重要的—那是我们的对象大多数时间要做的。至少我们希望如此。然而,当客户行为不当时正确的行为对保证一个稳定和可预测的系统同样是重要的。
添加假的观察者
当前addObserver的实现轻率的接受任意类型的参数。尽管我们的实现能用任意函数作为观察者,它不能处理处理任意值。下面的测试期望当试图添加一个不可调用的观察者时被观察者抛出一个异常。
1. "test should throw for uncallable observer": function () { 2. var observable = new tddjs.Observable(); 3. assertException(function () { 4. observable.addObserver({}); 5. }, "TypeError"); 6. }
|
通过当添加一个观察者时抛出一个异常,当我们通知观察者时,我们不需要担心非法数据。我们按契约编程,我们说addObserver
方法的前置条件是输入必须可调用。后置条件是观察者被添加到被观察者中并且保证一旦被观察者调用notify会被调用。
测试失败,所以我们把注意力移到尽快的再次得到绿条。不幸的是,这没有办法做一个假的实现—在任意调用addObserver
时抛出一个异常,那会使其他所有的测试失败。幸运的是,这个实现相当的容易:
1. function addObserver(observer) { 2. if (typeof observer != "function") { 3. throw new TypeError("observer is not function"); 4. } 5. this.observers.push(observer); 6. }
|
addObserver 现在在添加观察者到列表之前检查了观察者确实是一个函数。运行测试又感受到了成功的亲切:所有都绿色的。
不正确行为的观察者
被观察者现在保证任何添加到addObserver的观察者是可调用的。如果一个观察者抛出异常,Notify仍然会意外的失败。下一个测试预期即使其中的一个观察者抛出异常时所有的观察者都会被调用。
1. "test should notify all even when some fail": function () { 2. var observable = new tddjs.Observable(); 3. var observer1 = function () { throw new Error("Oops"); }; 4. var observer2 = function () { observer2.called = true; }; 5. observable.addObserver(observer1); 6. observable.addObserver(observer2); 7. observable.notify(); 8. assertTrue(observer2.called); 9. }
|
运行测试暴露了当前实现随着第一个观察者崩溃了,导致第二个观察者没有被调用。实际上,notify打破了它保证一旦观察者被成功添加就总是调用所有的观察者的保证。为改变这个状况,方法需要为最坏的情况做好准备:
1. function notify() { 2. for (var i = 0, l = this.observers.length; i < l; i++) { 3. try { 4. this.observers[i].apply(this, arguments); 5. } catch (e) {} 6. } 7. } |
异常被默默的丢弃。观察者的责任是保证任何错误都被正确的处理,被观察者则简单的抵挡不良行为的观察者。
记录调用顺序
通过适当的错误处理我们已经改进了被观察者模块的健壮性。这个模块现在能给操作以保证,只要它能得到良好的输入,并且恢复观察者遇到失败的要求(真拗口)。然而,最后添加的测试对被观察者未文档化的特性假定:它假设观察者按我们添加的顺序被调用。一般的,这种方案是可工作的,因为我们用数组实现观察者列表。我们决定改变一下,可是,我们的测试可能被打破。所以我们需要决定:我们要重构测试为不假定调用顺序,或者简单添加一个验证调用顺序的测试—从而记录调用顺序作为一个特征?调用顺序好像一个敏感的特征,所以我们下一个测试确认被观察者保持这个行为。
1. "test should call observers in the order they were added": 2. function () { 3. var observable = new tddjs.Observable(); 4. var calls = []; 5. var observer1 = function () { calls.push(observer1); }; 6. var observer2 = function () { calls.push(observer2); }; 7. observable.addObserver(observer1); 8. observable.addObserver(observer2); 9. observable.notify(); 10. assertEquals(observer1, calls[0]); 11. assertEquals(observer2, calls[1]); 12. }
|
自从实现已经使用一个观察者数组,这个测试立即就成功了。
观察任意对象
使用经典继承的静态语言,任意对象被被观察者通过子类化被观察者生成。在这些案例中经典继承的动机来自于一个愿望:定义在一个地方的模式的结构,并重用交叉的大量无关对象的逻辑。在JavaScript中,要在对象之间重用代码我们有几种选择,所以我们不需要限定自己模拟经典的继承模型。
为了破坏经典模拟构造器提供者的自由(这么别扭呢,原文:Inthe interest of breaking
free of the classical emulation that constructorsprovide),考虑下面的例子,假定tddjs.observable是一个对象而不是构造器:
注意:tddjs.extend 方法在书中引入其他对象并从另一个对象简单复制属性。
[javascript] view plaincopy
1. // Creating a single observable object 2. var observable = Object.create(tddjs.util.observable); 3. // Extending a single object 4. tddjs.extend(newspaper, tddjs.util.observable); 5. // A constructor that creates observable objects 6. function Newspaper() { 7. /* ... */ 8. } 9. Newspaper.prototype = Object.create(tddjs.util.observable); 10. // Extending an existing prototype 11. tddjs.extend(Newspaper.prototype, tddjs.util.observable);
|
简单实现被观察者作为一个简单对象,提供极大的灵活性。为了达到这个目标,我们需要重构现有的方案去除构造器。
废弃构造器
为去除构造器,我们首先重构被观察者让构造器不做任何事。幸运的是,构造器只初始观察者数组,这不难移除。所有Observable.prototype中存取数组的方法,我们需要确保它们能全部处理它不被初始化的情况。为测试这种情况,我们仅仅需要写一个测试,每个有问题的方法做任何其他事情之前,首先调用该方法。
因为我们在做其他事之前,已经测试了调用addObserver 和hasObserver ,我们专注于notify方法。这个方法只是在addObserver
被调用之后测试。我们下一个测试期望它能首先调用这个方法添加任何对象。
1. "test should not fail if no observers": function () { 2. var observable = new tddjs.Observable(); 3. assertNoException(function () { 4. observable.notify(); 5. }); 6. } |
有这个测试在,我们可以清空构造器。
1. function Observable() { 2. }
|
运行这个测试显示除了一个外全部失败,所有的都是相同的信息:“this.observers未定义”。我们一次处理一个方法。首先是addObserver
方法:
function addObserver(observer) { if (!this.observers) { this.observers = []; }
/* ... */
} |
再次运行测试显示更新的addObserver 修正了除两个开始不调用它的测试之外的所有其他测试。接着,我们确保如果数组不存在,hasObserver
直接返回false。
1. function hasObserver(observer) { 2. if (!this.observers) { 3. return false; 4. } 5. /* ... */ 6. }
|
我们可以使用完全相同的方法修正notify:
1. function notify(observer) { 2. if (!this.observers) { 3. return; 4. } 5. /* ... */ 6. }
|
用对象替换构造器
现在构造器已经不做任何事,可以安全的移除它。然后我们直接添加所有的方法到tddjs.observable
对象,它可以用作例如Object.create或tddjs.extend来创建被观察者对象。注意名称不再大写,因为它不再是一个构造器。更新后的实现如下:
1. (function () { 2. function addObserver(observer) { 3. /* ... */ 4. } 5. function hasObserver(observer) { 6. /* ... */ 7. } 8. function notify() { 9. /* ... */ 10. } 11. tddjs.observable = { 12. addObserver: addObserver, 13. hasObserver: hasObserver, 14. notify: notify 15. }; 16. }());
|
当然,移除的构造器导致所有的测试都打破,然而,修正他们很容易。所有需要我们做的是用Object.create调用更换新的语句。然而,绝大多数浏览器还不支持Object.create,所以我们自己实现它(原文:sowe
can shim it.但我觉得这样翻译更好理解)。因为该方法不能完全的模拟,我们在tddjs object上提供我们自己的版本:
1. (function () { 2. function F() {} 3. tddjs.create = function (object) { 4. F.prototype = object; 5. return new F(); 6. }; 7. /* Observable implementation goes here ... */ 8. }());
|
随着自己实现的到位,我们可以在一个事件中更新测试,那样它甚至可以在老浏览器上工作。最终的测试套件如下:
1. TestCase("ObservableAddObserverTest", { 2. setUp: function () { 3. this.observable = tddjs.create(tddjs.observable); 4. }, 5. "test should store functions": function () { 6. var observers = [function () {}, function () {}]; 7. this.observable.addObserver(observers[0]); 8. this.observable.addObserver(observers[1]); 9. assertTrue(this.observable.hasObserver(observers[0])); 10. assertTrue(this.observable.hasObserver(observers[1])); 11. } 12. }); 13. TestCase("ObservableHasObserverTest", { 14. setUp: function () { 15. this.observable = tddjs.create(tddjs.observable); 16. }, 17. "test should return false when no observers": function () { 18. assertFalse(this.observable.hasObserver(function () {})); 19. } 20. }); 21. TestCase("ObservableNotifyTest", { 22. setUp: function () { 23. this.observable = tddjs.create(tddjs.observable); 24. }, 25. "test should call all observers": function () { 26. var observer1 = function () { observer1.called = true; }; 27. var observer2 = function () { observer2.called = true; }; 28. this.observable.addObserver(observer1); 29. this.observable.addObserver(observer2); 30. this.observable.notify(); 31. assertTrue(observer1.called); 32. assertTrue(observer2.called); 33. }, 34. "test should pass through arguments": function () { 35. var actual; 36. this.observable.addObserver(function () { 37. actual = arguments; 38. }); 39. this.observable.notify("String", 1, 32); 40. assertEquals(["String", 1, 32], actual); 41. }, 42. "test should throw for uncallable observer": function () { 43. var observable = this.observable; 44. assertException(function () { 45. observable.addObserver({}); 46. }, "TypeError"); 47. }, 48. "test should notify all even when some fail": function () { 49. var observer1 = function () { throw new Error("Oops"); }; 50. var observer2 = function () { observer2.called = true; }; 51. this.observable.addObserver(observer1); 52. this.observable.addObserver(observer2); 53. this.observable.notify(); 54. assertTrue(observer2.called); 55. }, 56. "test should call observers in the order they were added": 57. function () { 58. var calls = []; 59. var observer1 = function () { calls.push(observer1); }; 60. var observer2 = function () { calls.push(observer2); }; 61. this.observable.addObserver(observer1); 62. this.observable.addObserver(observer2); 63. this.observable.notify(); 64. assertEquals(observer1, calls[0]); 65. assertEquals(observer2, calls[1]); 66. }, 67. "test should not fail if no observers": function () { 68. var observable = this.observable; 69. assertNoException(function () { 70. observable.notify(); 71. }); 72. } 73. });
|
为了避免重新调用tddjs.create,每个测试用例得到一个setUp 方法,它创建测试的被观察者对象。测试方法不得不做相应的更新,用this.observable替换observable。
|