在现在的软件开发中,单元测试已经变得越来越重要了.相比程序员与测试QA的手工测试,单元测试可以在项目每次build的时候集成运行,来为程序员提供Regression Test的反馈.这在敏捷开发中为程序员提供了很重要的支持,可以很容易的进行重构. 现在的主流编程语言都有很好的对单元测试的支持, 关于JUnit和NUnit的文章也已经有很多了. 在这里,我们介绍一下如何使用QUnit对Javascript脚本进行单元测试.
在这里我们使用一个简单的扑克的例子. 首先,我们用Javascript写一个简单的扑克牌类:
var Card = function(opts) { var C = {}; C._normalizeArg = function(arg) { if (_.isString(arg)) { arg = arg.replace(/\s+/g,'').split(","); } if (!_.isArray(arg)) { arg = [arg]; } return arg; }; C.extend = function(obj) { _(C).extend(obj); return C; }; C.CardKind = { "Spade" : "spade", "Heart" : "heart", "Diamond" : "diamond", "Club" : "club", "Special" : "special" }; C.CardNum = { "Ace" : 1, "Two" : 2, "Three" : 3, "Four" : 4, "Five" : 5, "Six" : 6, "Seven" : 7, "Eight" : 8, "Nine" : 9, "Ten" : 10, "Jack" : 11, "Queen" : 12, "King" : 13, "JokerS" : 14, "Joker" : 15 }; C.Card = Class.extend({ init : function(kind, num) { this.kind = kind; this.num = num; }, name : function() { return this.kind + this.num; }, isComparable : function(card) { return this.kind == card.kind; }, compareTo : function (card) { if (this.isComparable(card)) { return this.num - card.num; } } }); C.Deck = Class.extend({ init : function(numOfDecks, includeJokers, jokersAreDifferent, cards) { this.numOfDecks = numOfDecks == undefined ? 1 : numOfDecks; this.includeJokers = includeJokers == undefined ? false : includeJokers; this.jokersAreDifferent = jokersAreDifferent == undefined ? false : jokersAreDifferent; this.cards = []; this.setup(cards); }, setup : function(cards) { if (cards == undefined) { var kinds = _.filter(C.CardKind, function(kind) {return kind != C.CardKind.Special; }); var nums = _.filter(C.CardNum, function(num) {return num <= C.CardNum.King; }); for (var i = 1; i <= this.numOfDecks; i++) { for (var kind in kinds) { for (var num in nums) { this.cards.push(new C.Card(kinds[kind], nums[num])); } } if (this.includeJokers) { if (this.jokersAreDifferent) { this.cards.push(new C.Card(C.CardKind.Special, C.CardNum.JokerS)); this.cards.push(new C.Card(C.CardKind.Special, C.CardNum.Joker)); } else { this.cards.push(new C.Card(C.CardKind.Special, C.CardNum.Joker)); this.cards.push(new C.Card(C.CardKind.Special, C.CardNum.Joker)); } } } } else { this.cards = cards; } var cardIndexes = new Array(); var currentIndex = -1; for (var i = 0; i < this.totalNumOfCards(); i++) { cardIndexes[i] = i; } this.currentCard = function() { return this.cards[cardIndexes[currentIndex]]; }; this.shuffle = function() { cardIndexes = _.shuffle(cardIndexes); currentIndex = -1; return this; }; this.availableNumOfCards = function() { return this.totalNumOfCards() - currentIndex - 1; }; this.getCard = function() { if (this.availableNumOfCards() > 0) { currentIndex++; return this.currentCard(); } }; this.skip = function(num) { if (this.availableNumOfCards() >= num) { currentIndex += num; } return this; } }, totalNumOfCards : function() { return this.cards.length; } }); return C; };
这个简单的类定义了一副扑克牌的54张牌, 和一个Deck类,提供对一副牌的生成和一些简单方法. 下面我们添加对这些方法的单元测试:
test('Card.init', function() { var C = Card(); var card = new C.Card(C.CardKind.Club, C.CardNum.Ace); QUnit.equal(card.name(), 'club1', 'card Club Ace has name club1'); var card = new C.Card(C.CardKind.Special, C.CardNum.Joker); QUnit.equal(card.name(), 'special15', 'card Special Joker has name special15'); }); test('Card.isComparable', function() { var C = Card(); var card1 = new C.Card(C.CardKind.Club, C.CardNum.Ace); var card2 = new C.Card(C.CardKind.Club, C.CardNum.Two); QUnit.equal(card1.isComparable(card2), true, 'Club Ace is comparable with Club Two'); var C = Card(); var card1 = new C.Card(C.CardKind.Club, C.CardNum.Ace); var card2 = new C.Card(C.CardKind.Heart, C.CardNum.Two); QUnit.equal(card1.isComparable(card2), false, 'Club Ace is not comparable with Heart Two'); }); test('Card.compareTo', function() { var C = Card(); var card1 = new C.Card(C.CardKind.Club, C.CardNum.Ace); var card2 = new C.Card(C.CardKind.Heart, C.CardNum.Two); QUnit.equal(card1.compareTo(card2) == undefined, true, 'Club Ace compares to Heart Two gets undefined'); var C = Card(); var card1 = new C.Card(C.CardKind.Club, C.CardNum.Ace); var card2 = new C.Card(C.CardKind.Club, C.CardNum.Two); QUnit.equal(card1.compareTo(card2) < 0, true, 'Club Ace is smaller to Club Two'); var C = Card(); var card1 = new C.Card(C.CardKind.Club, C.CardNum.Ace); var card2 = new C.Card(C.CardKind.Club, C.CardNum.Ace); QUnit.equal(card1.compareTo(card2) == 0, true, 'Club Ace equals to Club Ace'); }); test('Deck.init(numOfDecks : 1)', function() { var C = Card(); var deck = new C.Deck(); QUnit.equal(deck.totalNumOfCards(), 52, '1 deck contains 52 cards'); QUnit.equal(_.all(deck.cards, function(card) { return card.kind != C.CardKind.Special && card.num <= C.CardNum.King; }), true, 'There is no jokers'); var counts = _.countBy(deck.cards, function(card) { return card.kind; }); QUnit.equal(counts[C.CardKind.Club], 13, '13 club cards'); QUnit.equal(counts[C.CardKind.Diamond], 13, '13 diamond cards'); QUnit.equal(counts[C.CardKind.Heart], 13, '13 heart cards'); QUnit.equal(counts[C.CardKind.Spade], 13, '13 spade cards'); var counts2 = _.countBy(deck.cards, function(card) { return card.num; }); QUnit.equal(counts2[C.CardNum.Ace], 4, '4 Ace cards'); QUnit.equal(counts2[C.CardNum.Two], 4, '4 Two cards'); QUnit.equal(counts2[C.CardNum.Three], 4, '4 Three cards'); QUnit.equal(counts2[C.CardNum.Four], 4, '4 Four cards'); QUnit.equal(counts2[C.CardNum.Five], 4, '4 Five cards'); QUnit.equal(counts2[C.CardNum.Six], 4, '4 Six cards'); QUnit.equal(counts2[C.CardNum.Seven], 4, '4 Seven cards'); QUnit.equal(counts2[C.CardNum.Eight], 4, '4 Eight cards'); QUnit.equal(counts2[C.CardNum.Nine], 4, '4 Nine cards'); QUnit.equal(counts2[C.CardNum.Ten], 4, '4 Ten cards'); QUnit.equal(counts2[C.CardNum.Jack], 4, '4 Jack cards'); QUnit.equal(counts2[C.CardNum.Queen], 4, '4 Queen cards'); QUnit.equal(counts2[C.CardNum.King], 4, '4 King cards'); }); test('Deck.init with Jokers', function() { var C = Card(); var deck = new C.Deck(1, true, true); QUnit.equal(deck.totalNumOfCards(), 54, '1 deck contains 52 cards and 2 jokers'); var counts = _.countBy(deck.cards, function(card) { return card.kind; }); QUnit.equal(counts[C.CardKind.Special], 2, '2 jokers'); var counts2 = _.countBy(deck.cards, function(card) { return card.num; }); QUnit.equal(counts2[C.CardNum.JokerS], 1, '1 small joker'); QUnit.equal(counts2[C.CardNum.Joker], 1, '1 big joker'); var C = Card(); var deck = new C.Deck(1, true, false); QUnit.equal(deck.totalNumOfCards(), 54, '1 deck contains 52 cards and 2 jokers'); var counts = _.countBy(deck.cards, function(card) { return card.kind; }); QUnit.equal(counts[C.CardKind.Special], 2, '2 jokers'); var counts2 = _.countBy(deck.cards, function(card) { return card.num; }); QUnit.equal(counts2[C.CardNum.JokerS], undefined, 'there is no small joker'); QUnit.equal(counts2[C.CardNum.Joker], 2, '2 big jokers'); }); test('Deck.utilities', function() { var C = Card(); var deck = new C.Deck(); QUnit.equal(deck.currentCard() == undefined, true, "call current card without getting the first card gets no card"); QUnit.equal(deck.availableNumOfCards(), deck.totalNumOfCards(), "all cards are available"); QUnit.equal(deck.getCard() == deck.cards[0], true, "getCard gets the first card without shuffle"); QUnit.equal(deck.getCard().compareTo(deck.cards[1]), 0, "getCard call again gets the second card without shuffle using compareTo"); QUnit.equal(deck.shuffle() instanceof C.Deck, true, "shuffle function returns the deck back"); QUnit.equal(deck.availableNumOfCards(), deck.totalNumOfCards(), "after shuffle, the deck is reset"); QUnit.equal(deck.skip(deck.totalNumOfCards()).availableNumOfCards(), 0, "skipping all cards gets no card left"); QUnit.equal(deck.getCard() == undefined, true, "call getCard with no card available gets no card"); });
最后,我们只需要写一个简单的网页来运行测试:
<!DOCTYPE html> <html> <head> <title>QUnit Test Suite</title> <link rel="stylesheet" href="lib/qunit.css" type="text/css" media="screen"> <script type="text/javascript" src="lib/qunit.js"></script> <!-- Your project file goes here --> <script type="text/javascript" src='jquery.min.js'></script> <script type="text/javascript" src='underscore.js'></script> <script type="text/javascript" src="quintus.js"></script> <script type="text/javascript" src="card.js"></script> <!-- Your tests file goes here --> <script type="text/javascript" src="card_test.js"></script> </head> <body> <h1 id="qunit-header">QUnit Test Suite</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> </body> </html>
运行上面的网页, 我们就可以看到测试结果了. 上面的代码可以在https://github.com/mcai4gl2/card上下载, 上面的代码对应card.js, card_test.js和cardTest.html这三个文件. 运行cardTest.html并不需要一个Web Server, 只需要在浏览器中打开这个文件就可以了. 运行效果可以在http://card1php.eu01.aws.af.cm/cardTest.html看到.
作者:mcai4gl2 发表于2013-12-8 2:56:00 原文链接
阅读:137 评论:0 查看评论