本篇文章是QUnit的简介,可以作为很好的入门教程。文章原址
介绍
自动化测试时软件开发过程中必不可少的一部分,而单元测试则是自动化测试的最为基本的一块,软件的每一个组件,
每一个功能单元都需要经过不停地测试以保证在上线时可以正常的工作。当然,测试也不仅仅只有这些作用,最为人
所知的就是测试驱动设计(test-driven design),测试先于实现进行。先写出一个简单的测试,当然此时运行肯定会
出错,然后我们在开始完成具体的实现,直到测试通过。如果仅仅依靠我们自己实现测试函数,不仅仅难以抓住测试过程
中的各种细节(错误代码,错误位置,执行结果与预期结果差异),而且各个浏览器的兼容性也会让我们抓狂,所以我们
亟需一个解决浏览器差异性和提供详细信息的测试框架,John Resig大神写出了QUnit框架。我等凡人尽管写不出这种富有
调整型的框架,但是很有必要学会使用QUnit。
自动化单元测试
问题
我们想对应用进行单元测试,或者也想从测试驱动设计获益,此时我们需要自己手动写测试函数。但是正如上文所提,测试
细节信息和浏览器兼容性我们难以搞定,我们需要解决它。
解决方案
我们可以使用QUnit,在使用QUnit钱,需要引入qunit.js和qunit.css文件
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>QUnit basic example</title> <link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.16.0.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="//code.jquery.com/qunit/qunit-1.16.0.js"></script> <script> QUnit.test( "a basic test example", function( assert ) { var value = "hello"; assert.equal( value, "hello", "We expect value to be hello" ); }); </script> </body> </html>
id为qunit-fixture的div标签是必须的,在所有的QUnit测试中都是一样,它是测试夹具。每次测试完都会讲测试夹具内的
内容清空。
我们可以发现并没有在显示的在documentReady之后执行测试,这是因为测试器将会控制测试的开始执行时间,在执行QUnit.test
时,将函数添加到一个等待队列中,知道测试器调用。
断言结果
问题
断言是单元测试必不可少的要素之一。使用者需要将预期的结果和让测试器执行具体实现的结果进行比对,并得出相应
的判断。
方案
QUnit提供了简单的断言。
ok( truthy [, message ] )
最基本的断言就是ok(),它必须提供一个boolean参数(或者可以转化为boolean的参数,在js中貌似都可以类型转换为boolean),
另外一个参数可选。
QUnit.test( "ok test", function( assert ) { assert.ok( true, "true succeeds" ); assert.ok( "non-empty", "non-empty string succeeds" ); assert.ok( false, "false fails" ); assert.ok( 0, "0 fails" ); assert.ok( NaN, "NaN fails" ); assert.ok( "", "empty string fails" ); assert.ok( null, "null fails" ); assert.ok( undefined, "undefined fails" ); });
equal( actual, expected [, message ] )
equal用“==”操作符来比较actual和expected参数,message为测试通过显示的信息。
QUnit.test( "equal test", function( assert ) { 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 ] )
该方法对简单的数值类型也是使用“===”进行比较。而对于对象,则不是使用“===”。它会便利对象的属性,并比较键值对。
(键和值都会比较)。
QUnit.test( "deepEqual test", function( assert ) { var obj = { foo: "bar" }; assert.deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" ); });
同步回调
问题
在回调模式中,放在回调函数中的代码可能会阻止断言执行,所以需要通过某种方式提醒断言是否执行。
方案
我们可以使用assert.expect(n)来设定预期执行断言的数量。如果没有执行预期数量的断言,将会报错。
QUnit.test( "a test", function( assert ) { assert.expect( 2 ); function calc( x, operation ) { return operation( x ); } var result = calc( 2, function( x ) { assert.ok( true, "calc() calls operation function" ); return x * x; }); assert.equal( result, 4, "2 square equals 4" ); });
异步回调
问题
异步回调与测试器的队列和运行测试的方式有冲突。在该测试之后的测试函数将不会被执行。
方案
在异步回调中使用assert之后,调用assert.aysnc(),他会返回一个done函数,在测试执行完毕调用done函数。
经过测试,如果不调用done函数,则末尾切片函数将不会被执行,之后的测试函数也不会被执行。
QUnit.test( "asynchronous test: async input focus", function( assert ) { var done = assert.async(); var input = $( "#test-input" ).focus(); setTimeout(function() { assert.equal( document.activeElement, input[0], "Input was focused" ); done(); }); });
测试的原子性
在每次执行test之后,QUnit都会重置 #qunit-fixture的dom div,包括在内的事件。只要测试代码在测试夹具内,不用
手动对夹具进行清理。
组测试
组测试可以保证逻辑类似的测试在一起执行,而且可以添加切片函数。该函数在测试之前和之后执行。
QUnit.module( "module", { setup: function( assert ) { assert.ok( true, "one extra assert per test" ); }, teardown: function( assert ) { assert.ok( true, "and one extra assert after each test" ); } }); QUnit.test( "test with setup and teardown", function() { assert.expect( 2 ); });
Efficient Development
Problem
Once your testsuite takes longer than a few seconds to run, you want to avoid wasting a lot of time just waiting for test results to come in.
Solution
QUnit has a bunch of features built in to make up for that. The most interesting ones require just a single click to activate. Toggle the "Hide passed tests" checkbox at the top, and QUnit will only show you tests that failed. That alone doesn‘t make a difference in speed, but already helps focusing on failing tests.
It gets more interesting if you take another QUnit feature into account, which is enabled by default and usually not noticable. Whenever a test fails, QUnit stores the name of that test in sessionStorage
. The next time you run a testsuite, that failing test will run before all other tests. The output order isn‘t affected, only the execution order. In combination with the "Hide passed tests" checkbox you will then get to see the failing test, if it still fails, at the top, as soon as possible.
Discussion
The automatic reordering happens by default. It implies that your tests need to be atomic, as discussed previously. If your tests aren‘t, you‘ll see random non-deterministic errors. Fixing that is usually the right approach. If you‘re really desperate, you can set QUnit.config.reorder = false
.
In addition to the automatic reordering, there are a few manual options available. You can rerun any test by clicking the "Rerun" link next to that test. That will add a "testNumber=N" parameter to the query string, where "N" is the number of the test you clicked. You can then reload the page to keep running just that test, or use the browser‘s back button to go back to running all tests.
Running all tests within a module works pretty much the same way, except that you choose the module to run using the select at the top right. It‘ll set a "module=N" query string, where "N" is the encoded name of the module, for example "?module=testEnvironment%20with%20object".