技术开发 频道

使用JsUnit和JSMock的JavaScript测试驱动开发

  Stubs vs. Mocks

  让我们通过老虎机这一例子,看一看stubs和mock对象。由于这个单元测试关注单个对象,我们创建一个老虎机,并把它当作被测系统。现在让我们写一个简单的测试,生成老虎机。

1 function testRender() {
2    var buttonStub = {};
3    var balanceStub = {};
4    var reelsStub = [{},{},{}];
5    var randomNumbers = [2, 1, 3];
6    var randomStub = function(){return randomNumbers.shift();};
7
8
9    var slotMachine = new drw.SlotMachine(buttonStub, balanceStub, reelsStub,
10 randomStub);
11    slotMachine.render();
12
13
14    assertEquals('Pay to play', buttonStub.value);
15    assertTrue(buttonStub.disabled);
16    assertEquals(0, balanceStub.innerHTML);
17    assertEquals('images/2.jpg', reelsStub[0].src);
18    assertEquals('images/1.jpg', reelsStub[1].src);
19    assertEquals('images/3.jpg', reelsStub[2].src);
20 }
21

  testRender函数使用了两个DOM元素的stub,把它们都注入到被测系统的构造函数中,并调用render方法。测试的最后对render方法的期望结果进行断言。请注意通过使用DOM元素的stub,我们可以测试render方法的结果,而不必实际去做任何事情,这些事情可能导致测试页面的其他测试失效。这种方法与使用真实DOM元素各有利弊。使用真实的DOM元素更容易发现跨浏览器不兼容的bug,但是如果每个测试最后或者tearDown时没有进行重置,你的测试本身也更容易带来bug。

  被测系统并未直接调用全局函数Math.random,来决定每个轴初始的图片状态。相反,老虎机是依赖创建时提供给它的参数,来得到这些数字。这让我们可以测试一段不确定的代码,好像完全可以预测一样。请注意测试没有覆盖浏览器原生的Math.random实现,从而避免了状态变化的风险和副作用。

  等等,等一会儿... 测试函数有不止一个断言,这样行吗?敏捷社区中部分人认为每个测试中有多于一个断言是邪恶的。然而,给用来赚钱的实际的应用程序很少会这么写测试套件。当亲眼看到JUnit框架本身实物测试套件中每个测试有多少断言时,相信会有很多人非常惊讶。

  对象的构造函数和render方法看上去是这样的:

1 /**
2 * Constructor for the slot machine.
3 */
4
5
6 drw.SlotMachine = function(buttonElement, balanceElement, reels, random, networkClient)
7 {
8   this.buttonElement = buttonElement;
9   this.balanceElement = balanceElement;
10   this.reels = reels;
11   this.random = random;
12   this.networkClient = networkClient;
13   this.balance = 0;
14 };
15
16
17 drw.SlotMachine.prototype.render = function() {
18   this.buttonElement.disabled = true;
19   this.buttonElement.value = 'Pay to play';
20   this.balanceElement.innerHTML = 0;
21   for(var i = 0; i < this.reels.length;){
22     this.reels[i++].src = 'images/' + this.random() + '.jpg';
23   }
24 };
25

  让我们往老虎机里放一些钱。在这个场景下,老虎机异步调用服务器端以返回用户余额。这很有挑战性,因为单元测试中没有网络,AJAX调用会失败。当我们编写单元测试时,我们应该尽量编写没有副作用的代码,IO当然也属于这一类。

1 function testGetBalanceGoesToNetwork(){
2   var url, callback;
3   var networkStub = {
4     send : function() {
5       url = arguments[0];
6       callback = arguments[1];
7     }
8   };
9
10
11   var slotMachine = new drw.SlotMachine(null, null, null, null, networkStub);
12
13
14   slotMachine.getBalance();
15
16
17   assertEquals('/getBalance.jsp', url);
18   assertEquals('function', typeof callback);
19 }
20

  这个测试使用了网络stub。什么是stub呢?stub与mock有什么区别呢?许多开发者经常混淆这个两个词,认为它们是同义词。测试社区中,stub这个词是保留给基于状态测试的。JavaScript中它通常是指一个简单的object literal,能够返回预先硬编码的数值。而mock这个词则是保留给交互测试的。Mock可以针对行为训练。这些行为与被测对象进行交互,并且可以验证这些交互。

  通过网络客户端stub,我们现在能够测试getBalance方法。通过本地变量url和callback,应用于构造函数的object literal stub能够记录它与被测系统的交互行为。这些本地变量使我们能够在测试的最后执行断言。不幸的是,我们用错工具了。这是一个经典例子,说明了stub的局限性,以及为什么使用mock对象。这个测试的目的不是在给了一定状态之后,验证被测系统的行为。测试关注的是drw.SlotMachine实例与它的一个协作者——网络客户端之间的交互。

0
相关文章