30分钟QUnit入门教程

30分钟Qunit入门教程

15分钟让你了解Javascript单元测试框架QUnit,并能在程序中使用。

QUnit是什么

QUnit是一个强大,易用的JavaScript单元测试框架,由jQuery团队的成员所开发,并且用在jQuery,jQuery UI,jQuery Mobile等项目。


Hello World

学习QUnit还是从例子开始最好,首先我们需要一个跑单元测试的页面,这里命名为index-test.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.17.1.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="http://code.jquery.com/qunit/qunit-1.17.1.js"></script>
  <script src="tests.js"></script>
</body>
</html>

这里主要引入了两个文件,一个是QUnit的CSS文件,一个是提供断言等功能的JS文件。

这里另外引入了一个tests.js文件,我们的测试用例就写在这个文件里面。

tests.js:

QUnit.test( "hello test", function( assert ) {
  assert.ok( "hello world" == "hello world", "Test hello wordl" );
});

页面载入完毕,QUnit就会自动运行test()方法,第一个参数是被测试的单元的标题,第二个参数,就是实际的而是代码,这里的参数assert为QUnit的断言对象,其中提供了不少断言方法,这里使用了ok()方法,ok()方法接受两个参数,第一个是表明测试是否通过的bool值,第二个则是需要输出的信息。

我们在浏览器中运行index-test.html,就会看到测试结果:

从上到下,可以看到有三个checkbox,这几个的作用,我们后面再说。然后看到浏览器的User-Agent信息。之后是总的测试信息,跑了几个断言,通过了几个,失败了几个。最后是详细信息。

假如我们稍微修改一下刚才的断言条件,改成!=

assert.ok( "hello world" != "hello world", "Test hello wordl" );

则会得到测试失败的信息:

详细信息中有错误的行号,以及diff信息等。


更多断言

上面介绍了assert.ok()方法,QUnit还提供了一些别的断言方法,这里再介绍几个常用的。

equal(actual, expected [,message])

equal()断言用的是简单的==来比较实际值和期望值,相同则通过,否则失败。

修改一下tests.js:

QUnit.test( "hello test", function( assert ) {
  //assert.ok( "hello world" == "hello world", "Test hello wordl" );
  assert.equal( 0, 0, "Zero, Zero; equal succeeds" );
  assert.equal( "", 0, "Empty, Zero; equal succeeds" );
  assert.equal( "", "", "Empty, Empty; equal succeeds" );
  assert.equal( 0, false, "Zero, false; equal succeeds" );

  assert.equal( "three", 3, "Three, 3; equal fails" );
  assert.equal( null, false, "null, false; equal fails" );
});

浏览器中运行:

如果你需要严格的比较,需要用strictEqual()方法。

deepEqual(actual, expected, [,message])

deepEqual()断言的用法和equal()差不多,它除了使用===操作符进行比较之外,还可以通过比较{key : value}是否相等,来比较两个对象是否相等。

QUnit.test( "deepEqual test", function( assert ) {
  var obj = { foo: "bar" };

  assert.deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" );
});

如果要显式的比较两个值,equal()也可以适用。一般来说,deepEqual()是个更好的选择。

同步回调

有时候,我们的测试用例包含回调函数,要在回调函数中进行断言。这里可以用到assert.expect()函数,它接受一个表示断言数量的int值,表示这个test里面,预计要跑多少个断言。这里为了方便,引入了jQuery库,在index-test.html中加入<script src="http://code.jquery.com/qunit/qunit-1.17.1.js"></script>

QUnit.test( "a test", function( assert ) {
  assert.expect( 1 );

  var $body = $( "body" );

  $body.on( "click", function() {
    assert.ok( true, "body was clicked!" );
  });

  $body.trigger( "click" );
});

异步回调

assert.expect()对同步的回调非常有用,但是对异步回调却不是那么适用。

稍微修改一下上面的例子:

QUnit.test( "a test", function( assert ) {
  var done = assert.async();
  var $body = $( "body" );

  $body.on( "click", function() {
    assert.ok( true, "body was clicked!" );
    done();
    $body.unbind(‘click‘);
  });

  setTimeout(function(){
    console.log("To click body")
    $body.trigger( "click" );
  }, 1000)

});

使用assert.async()返回一个”done”函数,当操作结束的时候,调用这个函数。另外我在”done”函数调用结束之后,把body的click事件给移除了,这个是为了方便我在点击结果网页的时候,不要出发多次done函数。

结果:

这里我们也可以使用QUnit.start()与QUnit.stop()来控制异步回调中断言的判断。

QUnit.test( "a test 1", function( assert ) {
  QUnit.stop()
  var $body = $( "body" );

  $body.on( "click", function() {
    assert.ok( true, "body was clicked!" );
    QUnit.start();
    $body.unbind(‘click‘);
  });

  setTimeout(function(){
    console.log("To click body")
    $body.trigger( "click" );
  }, 1000)

});

QUnit还提供了QUnit.asyncTest()方法来简化异步调用的测试,不需要自己手动调用QUnit.stop()方法,并且从函数名也可以更容易的让人知道这是个异步调用的测试。

QUnit.asyncTest( "a test 2", function( assert ) {
  var $body = $( "body" );

  $body.on( "click", function() {
    assert.ok( true, "body was clicked!" );
    QUnit.start();
    $body.unbind(‘click‘);
  });

  setTimeout(function(){
    console.log("To click body")
    $body.trigger( "click" );
  }, 1000)

});

原子性

保持测试用例之间互不干扰很重要,如果要测试DOM修改,我们可以使用#qunit-fixture这个元素。#qunit-fixture就好比是拿来练级的小怪,每次打死,下次来又会满血复活。

这个元素中你可以写任何初始的HTML,也可以置空,每个test()结束,都会恢复初始值。

QUnit.test( "Appends a span", function( assert ) {
  var fixture = $( "#qunit-fixture" );

  fixture.append( "<span>hello!</span>" );
  assert.equal( $( "span", fixture ).length, 1, "div added successfully!" );
});

QUnit.test( "Appends a span again", function( assert ) {
  var fixture = $( "#qunit-fixture" );

  fixture.append("<span>hello!</span>" );
  assert.equal( $( "span", fixture ).length, 1, "span added successfully!" );
});

这里我们无论对#qunit-fixture里面的东西做什么,下次测试开始的时候都会“满血复活”。

分组

在QUnit中可以对测试进行分组,并且可以指定只跑哪组测试。

分组需要使用QUnit.module()方法。我们可以将刚才我们测试的代码进行一个简单的分组。

QUnit.module("Group DOM Test")
QUnit.test( "Appends a span", function( assert ) {
  var fixture = $( "#qunit-fixture" );

  fixture.append( "<span>hello!</span>" );
  assert.equal( $( "span", fixture ).length, 1, "div added successfully!" );
});

QUnit.test( "Appends a span again", function( assert ) {
  var fixture = $( "#qunit-fixture" );

  fixture.append("<span>hello!</span>" );
  assert.equal( $( "span", fixture ).length, 1, "span added successfully!" );
});

QUnit.module("Group Async Test")
QUnit.test( "a test", function( assert ) {
  var done = assert.async();
  var $body = $( "body" );

  $body.on( "click", function() {
    assert.ok( true, "body was clicked!" );
    done();
    $body.unbind(‘click‘);
  });

  setTimeout(function(){
    console.log("To click body")
    $body.trigger( "click" );
  }, 1000)

});

结果网页中会多一个下拉框,可以选择分组。

并且module也支持在每个测试之前或之后做些准备工作。

QUnit.module("Group DOM Test", {
    setup: function(){
        console.log("Test setup");
    },
    teardown: function(){
        console.log("Test teardown");
    }
})

在执行这个分组的每个test()执行前后会分别运行setup()teardown()函数。

AJAX测试

AJAX在前端中占据了非常大的比重,由于AJAX的异步回调的复杂性,要做到业务代码和测试代码分离,也不容易,如果像jasmine框架中,用waitsFor来不停检查,超时等,其实不是太优雅。

这里结合jQuery,来一个比较优雅的,如果是使用别的框架,还需要另外研究。

不多说,直接上代码:

我们有一个进行ajax调用的对象:

var X = function () {
    this.fire = function () {
        return $.ajax({ url: "someURL", ... });
    };
};

然后是测试代码:

// create a function that counts down to `start()`
function createAsyncCounter(count) {
    count = count || 1; // count defaults to 1
    return function () { --count || QUnit.start(); };
}

// an async test that expects 2 assertions
QUnit.asyncTest("testing something asynchronous", 2, function(assert) {
    var countDown = createAsyncCounter(1), // the number of async calls in this test
        x = new X;

    // A `done` callback is the same as adding a `success` handler
    // in the ajax options. It‘s called after the "real" success handler.
    // I‘m assuming here, that `fire()` returns the xhr object
    x.fire().done(function(data, status, jqXHR) {
        assert.ok(data.ok);
        assert.equal(data.value, "123");
    }).always(countDown); // call `countDown` regardless of success/error
});

countDown方法是用来记录有多少个AJAX调用,然后在最后一个完成之后,调用QUnit.start()方法。QUnit.asyncTest中第二个参数”2”类似assert.expect( 2 )中的“2”。这里done()和always()方法是jQuery的deferred对象提供的,而$.ajax()会返回jqXHR对象,这个对象具有deferred对象的所有只读方法。

如果你需要记录一些错误信息,可以添加.fail()方法。

自定义断言

自定义断言,就是直接使用QUnit.push()封装一些自定义的判断。QUnit.push()assert.equal的关系就类似于$.ajax$.get的关系。

QUnit.assert.mod2 = function( value, expected, message ) {
    var actual = value % 2;
    this.push( actual === expected, actual, expected, message );
};

QUnit.test( "mod2", function( assert ) {
    assert.expect( 2 );

    assert.mod2( 2, 0, "2 % 2 == 0" );
    assert.mod2( 3, 1, "3 % 2 == 1" );
});

上面的代码自定义了一个叫mod2的断言。QUnit.push()有四个参数,一个Boolean型的result,一个实际值actual,一个期望值expected,以及一个说明message。

官网建议把自定义断言定义在全局的QUnit.assert对象上,方便重复利用。

调试工具与其他

最后我们来看看一开始说到的三个checkbox。

  • Hide passed tests

    很好理解,就是隐藏通过的测试,勾选之后,通过的测试就不显示出来了,在测试用例多的时候非常有用。而且使用了HTML5的sessionStorage技术,会记住之前没通过的测试,然后页面重新载入的时候只测试之前那部分没有通过的case。

  • Check for Globals

    “全局检查“,如果勾选了这项,在进行测试之前,QUnit会检查测试之前和测试之后window对象中的属性,如果前后不一样,就会显示不通过。

  • No try-catch

    选中则意味着QUnit会在try-catch语句之外运行回调,此时,如果测试抛出异常,测试就会停止。主要是因为有些浏览器的调试工具是相当弱的,尤其IE6,一个未处理的异常要比捕获的异常可以提供更多的信息。即使再次抛出,由于JavaScript不擅长异常处理,原来的堆栈跟踪在大多数浏览器里都丢失了。如果遇到一个异常,无法追溯错误代码的时候,就可以使用这个选项了。

另外每个测试旁边都有个”Rerun”的按钮,可以单独运行某个测试。


题外话

有些童鞋可能会问,单元测试真的有必要吗?

实际上,相信我们写完代码至少都会进行一些简单的输入输出测试,检查代码是否会报错。但是这相对比较手工,当我们代码的内部逻辑进行了一些改动,我们又需要进行一些测试,而且很容易漏掉一些测试,造成回归错误(改这里,造成那里出错)。如果我们有保留完整的单元测试代码,就可以方便的进行测试了。

同时,在进行每日构建的时候,都可以自动运行单元测试代码,让代码更健壮:-)

结语

好吧,我承认,我骗了你,读到这里,你肯定花了不止30分钟。既然你看到了这里,那证明我的阴谋成功了,被忽悠的感觉爽吧?

如果要投诉我,欢迎给我留言~

参考资料

QUnit官网

QUnit Cookbook

http://stackoverflow.com/questions/9431597/unit-testing-ajax-requests-with-qunit

http://www.zhangxinxu.com/wordpress/2013/04/qunit-javascript-unit-test-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/

时间: 2024-08-02 06:49:31

30分钟QUnit入门教程的相关文章

21分钟 MySQL 入门教程 &amp;&amp; mysql数据类型

转自:http://www.cnblogs.com/mr-wid/archive/2013/05/09/3068229.html#c1 http://www.cnblogs.com/zbseoag/archive/2013/03/19/2970004.html 21分钟 MySQL 入门教程 目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数据类型 五.使用MySQL数据库 登录

21分钟 MySQL 入门教程(转载!!!)

21分钟 MySQL 入门教程 目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数据类型 五.使用MySQL数据库 登录到MySQL 创建一个数据库 选择所要操作的数据库 创建数据库表 六.操作MySQL数据库 向表中插入数据 查询表中的数据 更新表中的数据 删除表中的数据 七.创建后的修改 添加列 修改列 删除列 重命名表 删除整张表 删除整个数据库 八.附录 修改 root

2016windows(10) wamp 最简单30分钟thrift入门使用讲解,实现php作为服务器和客户端的hello world

2016最简单windows(10) wamp 30分钟thrift入门使用讲解,实现php作为服务器和客户端的hello world thrift是什么 最简单解释 thrift是用来帮助各个编程语言之间进行通信,交换信息的一个框架(可以理解成一个工具,或者假象成一个软件).因为正常情况下php与java等语言无法进行数据的传递,当然平时我们运用接口传递jeson数据实现.但是使用thrift会有它的优势,我看到是说他的数据传输方式使得数据传输量很小,这方面可以自己搜索了解. 例如: 有人写了

Python 30分钟快速入门指南

学习地址 中文版:Python 30分钟入门指南 英文版:Learn X in Y minutes 学习时间 2019/03/10 19:00 - 19:32,多用了2分钟. 原文地址:https://www.cnblogs.com/huerxiong/p/10506664.html

论文神器Latex30分钟快速入门教程-只需9步向学神看齐

小E说:工欲善其事,必先利其器.立志做个安静的美学霸的你,学会Latex,一定能使你的论文写作事半功倍. 1.LaTeX软件的安装和使用 方法A(自助):在MikTeX的官网下载免费的MikTeX编译包并安装.下载WinEdt(收费)或TexMaker(免费)等编辑界面软件并安装. 方法B(打包):在ctex.org下载ctex套装(含MikTeX及WinEdt) 哈哈这一部分当然不包含在标题的30分钟里. 2.第一个文档 打开WinEdt,建立一个新文档,将以下内容复制进入文档中,保存,保存类

30分钟新手git教程

本文转载自:http://igeekbar.com/igeekbar/post/82.htm Git近些年的火爆程度非同一般,这个版本控制系统被广泛地用在大型开源项目(比如Linux),不同规模的团队开发,以及独立开发者,甚至学生之中. 初学者非常容易被git里的各种命令.参数吓得不愿意继续去学.但实际上刚上手的时候,你并不需要了解所有命令的用途.你可以从掌握一些简单.常用又强大的命令开始,然后逐步去学习.这就是我们这篇文章要讲的内容.让我们快开始吧! 基本了解 Git是一些命令行工具的集合,可

10分钟MySQL 入门教程

/***新建一个数据库名字叫LV_20140827,为了便于在命令提示符下显示中文, 在创建时通过 character set gbk 将数据库字符编码指定为 gbk**/create database LJ_20140827 character set gbk;/**查看已经创建了哪些数据库.**/show databases;/***选择所要操作的数据库要对一个数据库进行操作, 必须先选择该数据库, 否则会提示错误:ERROR 1046(3D000): No database selecte

21分钟 MySQL 入门教程

目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数据类型 五.使用MySQL数据库 登录到MySQL 创建一个数据库 选择所要操作的数据库 创建数据库表 六.操作MySQL数据库 向表中插入数据 查询表中的数据 更新表中的数据 删除表中的数据 七.创建后的修改 添加列 修改列 删除列 重命名表 删除整张表 删除整个数据库 八.附录 修改 root 用户密码 可视化管理工具 MyS

10分钟 MySQL 入门教程

一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数据类型 五.使用MySQL数据库 登录到MySQL 创建一个数据库 选择所要操作的数据库 创建数据库表 六.操作MySQL数据库 向表中插入数据 查询表中的数据 更新表中的数据 删除表中的数据 七.创建后的修改 添加列 修改列 删除列 重命名表 删除整张表 删除整个数据库 八.附录 修改 root 用户密码 可视化管理工具 MySQL