通过Qunit对Javascript代码进行单元测试

在现在的软件开发中,单元测试已经变得越来越重要了.相比程序员与测试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看到.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值