1、官方文档地址
http://qunitjs.com/cookbook/#asserting-results
Introduction 简介
Automated testing of software is an essential tool in development. Unit tests are the basic building blocks for automated tests: each component, the unit, of software is accompanied by a test that can be run by a test runner over and over again without any human interaction. In other words, you can write a test once and run it as often as necessary without any additional cost.
In addition to the benefits of good test coverage, testing can also drive the design of software, known as test-driven design, where a test is written before an implementation. You start writing a very simple test, verify that it fails (because the code to be tested doesn't exist yet), and then write the necessary implementation until the test passes. Once that happens, you extend the test to cover more of the desired functionality and implement again. By repeating those steps, the resulting code looks usually much different from what you'd get by starting with the implementation.
Unit testing in JavaScript isn't much different from in other programming languages. You need a small framework that provides a test runner, as well as some utilities to write the actual tests.
自动化的测试软件在开发中是必备的。单元测试将会为自动化测试创建一些基本的代码块:组件、单元,伴随着不需要人工的干预,一次一次的重复运行的测试程序。总之,你能编写一次测试就能在必要的时候再次运行而不用再次编写。
额外的好处就是代码运行的覆盖测试,同时测试也能驱动软件更好的设计,就像著名的测试驱动开发,在实现之前写一个测试,你甚至可以写一个简单的测试,然后测试失败(因为现在代码仍然没有开始编写),然后开始实现你的代码直到你的测试通过。你能够拓展你的测试去覆盖更多的需求和功能,并且再次扩充你的实现代码。通过重复这些步骤,最终你得到的代码看起来常常和你最初开始实现的代码大为不同。
Javasscript 的单元测试和其他计算机编程语言来说非常不同。因此你需要一个小的测试框架来运行你的测试用例,同时也作为一组编写单元测试的工具。
Automating Unit Testing自动单元测试
Problem 问题
You want to automate testing your applications and frameworks, maybe even benefit from test-driven design. Writing your own testing framework may be tempting, but it involves a lot of work to cover all the details and special requirements of testing JavaScript code in various browsers.
你想要自动测试nice应用和框架,也许从测试驱动开发的角度是非常有价值。写出一个自己的测试框架也许是很吸引人的,但是就需要涉及大量的工作关于JavaScript 代码在各个浏览器上全部的细节和个别特殊的要求。
Solution 解决方案
While there are other unit testing frameworks for JavaScript, you've decided to check out QUnit. QUnit is jQuery's unit test framework and is used by a wide variety of projects.
然而这里还有其他JavaScript测试框架,你最好决定签出Qunit,Qunit是Jquery的单元测试框架,并且被广泛使用在各个项目中。
To use QUnit, you only need to include two QUnit files on your HTML page. QUnit consists of qunit.js
, the test runner and testing framework, and qunit.css
, which styles the test suite page to display test results:
为了使用Qunit,你仅仅需要去包含2个Qunit文件在你的Html页面。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.15.0.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="//code.jquery.com/qunit/qunit-1.15.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>
打开浏览器可以看到下面的效果
The only markup necessary in the <body>
element is a <div>
with id="qunit-fixture"
. This is required for all QUnit tests, even when the element itself is empty. This provides the fixture for tests, which will be explained in the section called "Keeping Tests Atomic".
这里仅有的必要的标签是在<body>元素里卖弄放置一个<div>带上id="qunit-fixture"属性。这里获取到所有的测试用例,即使这个元素里面现在为空,这里提供了为测试的容器,将在the section called "Keeping Tests Atomic这节进一步解释。
The interesting part is the <script>
element following the qunit.js
include. It consists of a call to the test
function, with two arguments: the name of the test as a string, which is later used to display the test results, and a function. The function contains the actual testing code, which involves one or more assertions. The example uses one assertion, equal()
, which is explained in detail in Asserting Results.
这里最有趣的部分是引入qunit.js 的<script>元素。包括执行测试方法,使用2个参数,1个是被测试方法名作为字符串传入,随后作为显示测试结果,另外一个是一个方法,这个方法包含了测试用例代码,涉及一个或者多个断言。一个断言的例子是,equal(),详细解释在 Asserting Results.
Note that there is no document-ready
block. The test runner handles that: calling QUnit.test()
just adds the test to a queue, and its execution is deferred and controlled by the test runner.
注意并没有document-ready块,测试运行器的操作过程是,调用 qunit.test() 仅仅是添加测试到一个队列,然后随后被测试运行器控制盒执行。
Discussion 讨论
The header of the test suite displays the page title, a green bar when all tests passed (a red bar when at least one test failed), a bar with a few checkboxes to filter test results and a blue bar with the navigator.userAgent
string (handy for screenshots of test results in different browsers).
头部显示测试套件的标题,如果是绿色显示条,表示全部测试通过,如果至少有一个用例测试失败,这个显示条会显示红色,带有选择框这行用来过滤需要测试的用例,蓝色这行显示navigaor.userAgent 字符串,用来展示在不同浏览器下测试的结果差异。
Of the checkboxes, "Hide passed tests" is useful when a lot of tests ran and only a few failed. Checking the checkbox will hide everything that passed, making it easier to focus on the tests that failed (see also the Efficient Developmentsection below).
其中一个选择框,“Hide passed tests”是非常有用得,当大量的测试并且只有少数失败,选中这个选择框将隐藏通过的所有测试,这样可以更加容易的定位到特使失败的用例上去。(在 the Efficient Development 查看更多)
Checking "Check for Globals" causes QUnit to make a list of all properties on the window
object, before and after each test, then checking for differences. If properties are added or removed, the test will fail, listing the difference. This helps to make sure your test code and code under test doesn't accidentally export any global properties.
选中“Check for Globals”会使 Qunit 去生成一个所有属性在window对象上的清单,在每个用例之前和之后,然后检查差异。如果属性被增加和删除,测试将会失败,显示出差异。这样可以帮助我们确保测试代码和项目代码在测试中不会因为暴露出来的全局变量造成冲突。
The "No try-catch" checkbox tells QUnit to run your test outside of a try-catch block. When your test throws an exception, the testrunner will die, unable to continue running, but you'll get a "native" exception, which can help tremendously debugging old browsers with bad debugging support like Internet Explorer 6 (JavaScript sucks at rethrowing exceptions).
选中“"No try-catch”告诉QUnit 去运行测试不会使用 try-catch 。当你的项目代码抛出一个异常的的时候,测试运行器会崩溃,不在继续运行,但是你可以直观的看到异常,这样可以很好帮助你调试旧的浏览器,例如在非常差的调试支持的浏览器 IE6。
Below the header is a summary, showing the total time it took to run all tests as well as the overall number of total and failed assertions. While tests are still running, it will show which test is currently being executed.
下面一行,是一组统计,显示花费的时间,同事也更加直观的看到运行的所有测试用例的数量和成功的数量。当测试一直在运行的时候,数据也会随测试的运行而改变。
The actual contents of the page are the test results. Each entry in the numbered list starts with the name of the test followed by, in parentheses, the number of failed, passed, and total assertions. Clicking the entry will show the results of each assertion, usually with details about expected and actual results. The "Rerun" link at the end will run that test on its own.
最重要的内容是测试的结果。包含每个测试用例测更具编号排列,括号里显示失败,通过和用例的数量,点击进入将显示每个用例的结果,通畅由异常的详情和期望的结果组成。点击“Rerun”链接,可以重新单独运行本条测试。
Asserting Results 断言结果
Problem 问题
Essential elements of any unit test are assertions. The author of the test needs to express the results expected and have the unit testing framework compare them to the actual values that an implementation produces.
任何单元测试框架的必要的的元素就是断言。需要测试的作者表达期望的结果,然后单元测试框架会拿去和测试中产生的实际值做比较。
Solution 解决方案
QUnit provides a number of built-in assertions. Let's look at three of those:
QUnit提供了大量的内置断言,让我们看以下这几个:
ok( truthy [, message ] )
The most basic one is ok()
, which requires just one argument. If the argument evaluates to true, the assertion passes; otherwise, it fails. In addition, it accepts a string to display as a message in the test results:
使用最基本的一个就是ok(),仅仅只有一个请求的参数,如果返回值TRUE,这个断言通过,否则就失败。另外它接受1个字符串作为测试测试结果的显示。
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 ] )
The equal
assertion uses the simple comparison operator (==
) to compare the actual and expected arguments. When they are equal, the assertion passes; otherwise, it fails. When it fails, both actual and expected values are displayed in the test result, in addition to a given message:
这个 equal断言常常用于简单的使用(==)运算符比较期望参数和真实值,当他们相等的时候,断言通过,否则断言失败。这2个值都会在测试结果中显示,额外的也会显示测试消息。
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" ); });
Compared to ok()
, equal()
makes it much easier to debug tests that failed, because it's obvious which value caused the test to fail.
When you need a strict comparison (===
), use strictEqual()
instead.
相比ok(),equal() 可以更容易的测试失败,因为这是很明显的返回值不匹配就会失败。如果需要严格比较(===),使用 strccEqual() 代替。
deepEqual( actual, expected [, message ] )
The deepEqual()
assertion can be used just like equal()
and is a better choice in most cases. Instead of the simple comparison operator (==
), it uses the more accurate comparison operator (===
). That way, undefined
doesn't equal null
, 0
, or the empty string (""
). It also compares the content of objects so that {key: value}
is equal to {key: value}
, even when comparing two objects with distinct identities.
deepEqual()断言,能更好使用比 equal() 并且有更多选项。代替使用简单的比较运算符(==),使用更加准确的比较运算符(===)。这种情况下,undefined 不会等于 null,0 不会等于
空字符串。同时也能比较对象如{key:value} 会等于{key:value},即使使用严格模式(use strict)下的比较2个对象。
deepEqual()
also handles NaN, dates, regular expressions, arrays, and functions, while equal()
would just check the object identity:
deepEqual() 也能操作NaN,dates,正则表达式,数组,方法等类型,而 equal() 只能测试基本对象类型。
QUnit.test( "deepEqual test", function( assert ) { var obj = { foo: "bar" }; assert.deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" ); });
In case you want to explicitly not compare the content of two values, equal()
can still be used. In general, deepEqual()
is the better choice.
一般而言,只要你不需要非常准确的比较,equal()依然可以使用,只是通常来说,deepEqual() 是更好的选择。
Synchronous Callbacks 同步回调
Problem 问题
Occasionally, circumstances in your code may prevent callback assertions to never be called, causing the test to fail silently.
偶尔,你需要预防你的代码中因为有回调方法,永远也不会被调用,于是测试失败。
Solution 方案
QUnit provides a special assertion to define the number of assertions a test contains. When the test completes without the correct number of assertions, it will fail, no matter what result the other assertions, if any, produced.
Qunit提供了一个特别的包含了大部分测试内容的断言的定义。如果测试完成但是包含的这些断言有不正确的,测试失败,无论其他断言是否成功,如果没有,测试通过。
Usage is plain and simple; just call expect()
at the start of a test, with the number of expected assertions as the only argument:
使用的一个例子,调用 expect() 开始测试,使用这个期望断言数量作为参数
QUnit.test( "a test", function( 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" ); });
实际例子
QUnit.test( "a test", function( assert ) { expect( 1 ); var $body = $( "body" ); $body.on( "click", function() { assert.ok( true, "body was clicked!" ); }); $body.trigger( "click" ); });
Discussion 讨论
expect()
provides the most value when actually testing callbacks. When all code is running in the scope of the test function, expect()
provides no additional value—any error preventing assertions to run would cause the test to fail anyway, because the test runner catches the error and fails the unit.
expect()
提供了更多的测试回调的选项。当所有的代码运行在测试方法中的作用域的时候,expect()
没有额外的会导致运行测试失败的选项,因为测试运行器已经捕捉了错误并测试失败。
Asynchronous Callbacks 异步回调
Problem
While expect()
is useful to test synchronous callbacks (see the section called "Synchronous Callbacks"), it falls short when Asynchronous callbacks. Asynchronous callbacks conflict with the way the test runner queues and executes tests. When code under test starts a timeout or interval or an Ajax request, the test runner will just continue running the rest of the test, as well as other tests following it, instead of waiting for the result of the asynchronous operation.
expect()
在测试同步回调的时候是非常有用得,参考((see the section called "Synchronous Callbacks")),但是在测试异步回调变相形见绌。异步回调执行测试的方法和测试运行器执行队列冲突。当测试代码开始一个timeout 和 interval 或者一个ajax 请求。测试运行器仅仅简单的执行测,其他测试也是一样,而不是等待异步的返回。
Solution 解决
Instead of wrapping your assertions in a QUnit.test()
, use QUnit.asyncTest()
and call QUnit.start()
when your test block is complete and ready to resume:
代替使用 QUnit.test()来包裹测试断言,使用
QUnit.asyncTest()和
QUnit.start()当测试代码块完成准备处理回调方法:
QUnit.asyncTest( "asynchronous test: one second later!", function( assert ) { expect( 1 ); setTimeout(function() { assert.ok( true, "Passed and ready to resume!" ); QUnit.start(); }, 1000); });
一个实用的例子
QUnit.asyncTest( "asynchronous test: video ready to play", function( assert ) { expect( 1 ); var $video = $( "video" ); $video.on( "canplaythrough", function() { assert.ok( true, "video has loaded and is ready to play" ); QUnit.start(); }); });
Testing User Actions 测试用户行为
Problem
Code that relies on actions initiated by the user can't be tested by just calling a function. Usually an anonymous function is bound to an element's event, e.g., a click, which has to be simulated.
有时候代码依赖用户行为初始化,用户不能仅仅调用一个方法来测试。往往是一个被绑定到某个元素事件上的匿名方法,例如需要去模拟一个点击事件。
Solution
You can trigger the event using jQuery's trigger()
method and test that the expected behavior occurred. If you don't want the native browser events to be triggered, you can use triggerHandler()
to just execute the bound event handlers. This is useful when testing a click event on a link, where trigger()
would cause the browser to change the location, which is hardly desired behavior in a test.
你可以使用jQuery的tragger()触发一个一个时间然后测试这个行为导致的异常。如果你不想一个额外的浏览器时间呗触发,你可以使用 triggerHandler() 执行绑定在元素上的的方法。这是非常有用的在link 上测试click事件,如果trigger()则会导致浏览器产生一个默认的重定向操作,以至于我们测试它的行为变得困难.
Let's assume we have a simple key logger that we want to test:
我们假定有一个 简单的key logger方法需要测试
function KeyLogger( target ) { if ( !(this instanceof KeyLogger) ) { return new KeyLogger( target ); } this.target = target; this.log = []; var self = this; this.target.off( "keydown" ).on( "keydown", function( event ) { self.log.push( event.keyCode ); }); }
We can manually trigger a keypress event to see whether the logger is working:
我们手动触发一个按键然后看这个方法如何工作
QUnit.test( "keylogger api behavior", function( assert ) { var event, $doc = $( document ), keys = KeyLogger( $doc ); // trigger event event = $.Event( "keydown" ); event.keyCode = 9; $doc.trigger( event ); // verify expected behavior assert.equal( keys.log.length, 1, "a key was logged" ); assert.equal( keys.log[ 0 ], 9, "correct key was logged" ); });
Discussion 讨论
If your event handler doesn't rely on any specific properties of the event, you can just call .trigger(eventType)
. However, if your event handler does rely on specific properties of the event, you will need to create an event object using$.Event
and set the necessary properties, as shown previously.
如果你的事件响应方法不会依赖任何的特别的event 对象属性,你可以简单的使用 .tragger()。但是如果你的事件响应方法,依赖一些特别的属性和方法,你需要使用$.Event自定义创建一个event对象然后设置一些必要的属性,然后按照之前的方法操作。
It's also important to trigger all relevant events for complex behaviors such as dragging, which is comprised of mousedown, at least one mousemove, and a mouseup. Keep in mind that even some events that seem simple are actually compound; e.g., a click is really a mousedown, mouseup, and then click. Whether you actually need to trigger all three of these depends on the code under test. Triggering a click works for most cases.
对于非常复杂的行为比如拖拽依赖相关事件是非常重要的,比如mousedown相关的还有 mouseup和 mousemove,记住,有些事件非常简单,但是实际上有些麻烦,比如 click 事件,实际上会触发 mousedown mouseup,然后触发 click事件。无论您是否真的需要再测试中,触发这些依赖的事件。触发click都会导致更多的可能。
If thats not enough, you have a few framework options that help simulating user events:
如果这些还不够,你可以选用一些框架来模拟用户事件
- syn "is a synthetic event library that pretty much handles typing, clicking, moving, and dragging exactly how a real user would perform those actions". Used by FuncUnit, which is based on QUnit, for functional testing of web applications.
- JSRobot - "A testing utility for web-apps that can generate real keystrokes rather than simply simulating the JavaScript event firing. This allows the keystroke to trigger the built-in browser behaviour which isn't otherwise possible."
- DOH Robot "provides an API that enables testers to automate their UI tests using real, cross-platform, system-level input events". This gets you the closest to "real" browser events, but uses Java applets for that.
- keyvent.js - "Keyboard events simulator."
Keeping Tests Atomic 保持测试独立性
Problem 问题
When tests are lumped together, it's possible to have tests that should pass but fail or tests that should fail but pass. This is a result of a test having invalid results because of side effects of a previous test:
当很多测试一起运行时。可能出现本来应该通过的测试失败了,应该失败的测试通过了。这种结果是无效的,因为造成了后面的结果影响了前面的测试。
QUnit.test( "2 asserts", function( assert ) { var $fixture = $( "#qunit-fixture" ); $fixture.append( "<div>hello!</div>" ); assert.equal( $( "div", $fixture ).length, 1, "div added successfully!" ); $fixture.append( "<span>hello!</span>" ); assert.equal( $( "span", $fixture ).length, 1, "span added successfully!" ); });
The first append()
adds a <div>
that the second equal()
doesn't take into account.
第一个 append() 添加一个 <div> 导致第二个equal()不能成功运行。
Solution
Use the QUnit.test()
method to keep tests atomic, being careful to keep each assertion clean of any possible side effects. You should only rely on the fixture markup, inside the #qunit-fixture
element. Modifying and relying on anything else can have side effects:
使用 QUnit.test()方法保持测试独立,注意小心清理掉每个测试后造成的影响,你应该使用fixture 标记代替 #qunit-fixture 路由。修改然后没有任何依赖能产生邻近的影响。
QUnit.test( "Appends a div", function( assert ) { var $fixture = $( "#qunit-fixture" ); $fixture.append( "<div>hello!</div>" ); equal( $( "div", $fixture ).length, 1, "div added successfully!" ); }); QUnit.test( "Appends a span", function( assert ) { var $fixture = $( "#qunit-fixture" ); $fixture.append("<span>hello!</span>" ); assert.equal( $( "span", $fixture ).length, 1, "span added successfully!" ); });
QUnit will reset the elements inside the #qunit-fixture
element after each test, removing any events that may have existed. As long as you use elements only within this fixture, you don't have to manually clean up after your tests to keep them atomic.
QUnit 将重置#qunit-fixture元素里面的这个元素在每个测试完成之后,移除所有可能存在在事件。所以你可以全程使用 这个元素,也不用手动在每个测试完成之后清理这个元素 。
Discussion 讨论
In addition to the #qunit-fixture
fixture element and the filters explained in the section called "Efficient Development", QUnit also offers a ?noglobals
flag. Consider the following test:
更多的 #qunit-fixture 这个元素内容和这个元素的解释在 the section called "Efficient Development", QUnit also 提供了noglobals
标记,考虑了下面的测试
QUnit.test( "global pollution", function( assert ) { window.pollute = true; assert.ok( pollute, "nasty pollution" ); });
In a normal test run, this passes as a valid result. Running the ok()
test with the noglobals flag will cause the test to fail, because QUnit detected that it polluted the window object.
作为一个普通的测试,这个通过是无效的。运行 ok() 使用 noglobals flag 将导致测试失败。因为QUnit讲删除Window 对象。
There is no need to use this flag all the time, but it can be handy to detect global namespace pollution that may be problematic in combination with third-party libraries. And it helps to detect bugs in tests caused by side effects.
使用这个选项往往是不必要的,但是却能够控制全局命名空间导致的因为引入三方库产生的问题并且也能帮助处理因为相邻测试用例引发的BUG。
Grouping Tests 组测试
Problem 问题
You've split up all of your tests to keep them atomic and free of side effects, but you want to keep them logically organized and be able to run a specific group of tests on their own.
你可以拆开所有的测试用例,保持测试独立,避免相互影响,但是你也可能想要根据逻辑组织测试然后能够按照特别的组来运行你的测试。
Solution
You can use the QUnit.module()
function to group tests together:
你能使用 QUnit.module() 方法来每组一起测试。
QUnit.module( "group a" ); QUnit.test( "a basic test example", function( assert ) { assert.ok( true, "this test is fine" ); }); QUnit.test( "a basic test example 2", function( assert ) { assert.ok( true, "this test is fine" ); }); QUnit.module( "group b" ); QUnit.test( "a basic test example 3", function( assert ) { assert.ok( true, "this test is fine" ); }); QUnit.test( "a basic test example 4", function( assert ) { assert.ok( true, "this test is fine" ); });
All tests that occur after a call to QUnit.module()
will be grouped into that module. The test names will all be preceded by the module name in the test results. You can then use that module name to select tests to run (see the section called "Efficient Development").
所有的测试被打包进入模块 然后在调用QUnit.module()后一次性被执行。这个测试名将以模块名显示在测试结果中,你可以选择你需要的模块进行测试。具体看see the section called "Efficient Development"
Discussion
In addition to grouping tests, QUnit.module()
can be used to extract common code from tests within that module. TheQUnit.module()
function takes an optional second parameter to define functions to run before and after each test within the module:
关于更多的组合测试,QUnit.module() 能从模块内部解压相关代码,QUnit.module() 方法提供了第二个参数定义了这个模块运行时包含的前置用例和后置用例。
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() { expect( 2 ); });
You can specify both setup and teardown properties together, or just one of them.
Calling QUnit.module()
again without the additional argument will simply reset any setup/teardown functions defined by another module previously.
你可以一起使用 setup 和 teardown属性,也可以单独使用其中一个,调用 QUnit.module() 不带额外的参数 将使用之前的一个模块来重置这2个属性。
Custom Assertions 自定义断言
Problem 问题
You have several tests that duplicate logic for asserting some expectation. This repetitive code lessens the readability of your tests and increases the surface for bugs.
QUnit.test( "retrieving object keys", function( assert ) { var objectKeys = keys( { a: 1, b: 2 } ); assert.ok( objectKeys.indexOf("a") > -1, "Object keys" ); assert.ok( objectKeys.indexOf("b") > -1, "Object keys" ); var arrayKeys = keys( [1, 2] ); assert.ok( arrayKeys.indexOf("1") > -1, "Array keys" ); assert.ok( arrayKeys.indexOf("2") > -1, "Array keys" ); });
Solution
Define a function to encapsulate the expectation in a reusable unit. Invoke QUnit.push
within the body to notify QUnit that an assertion has taken place.
定义一个可以重复使用的方法。条用Qunit.push 在方法内部然后告诉QUnit断言已经发生。
QUnit.assert.contains = function( needle, haystack, message ) { var actual = haystack.indexOf(needle) > -1; QUnit.push(actual, actual, needle, message); }; QUnit.test("retrieving object keys", function( assert ) { var objectKeys = keys( { a: 1, b: 2 } ); assert.contains( "a", objectKeys, "Object keys" ); assert.contains( "b", objectKeys, "Object keys" ); var arrayKeys = keys( [1, 2] ); assert.contains( "1", arrayKeys, "Array keys" ); assert.contains( "2", arrayKeys, "Array keys" ); });
Discussion 讨论
Custom assertions can help make test suites more readable and more maintainable. At a minimum, they are simply functions that invoke QUnit.push
with a Boolean value--this is how QUnit detects that an assertion has taken place and the result of that assertion.
自定义断言可以使测试套件,更加可读和更加健壮。至少,这个简单的方法可以使用一个布尔值调用Qunit.push ,Qunit便能够知道断言已经产生或者断言的结果。
It is a good practice to define this function as a method on the global QUnit.assert
object. This helps communicate the purpose of the function to other developers. You may accomplish this by directly assigning a new property on the object (i.e. QUnit.assert.myAssertion = myAssertion;
) or using QUnit.extend
(i.e. QUnit.extend(QUnit.assert, { myAssertion: myAssertion });
).
一个非常适合练习的例子是定义全局的方法到Qunit.assert 。能够帮助其他的开发者交流这个方法的目的。你也可以在这个对象上定义一个新的属性。 (i.e. QUnit.assert.myAssertion = myAssertion;
) or using QUnit.extend
(i.e. QUnit.extend(QUnit.assert, { myAssertion: myAssertion });
).
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.
Qunit 提供给了一组特性对这些进行改进。仅仅通过鼠标点击即可开启,切换顶部的“Hide passed tests”选框,Qunit仅仅显示了失败的测试,单独使用不会有任何效果,但是帮助快速定位到失败的用例。
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.
另外一个有趣的的特性,默认开启但是不常常被注意到。当一个测试失败,Qunit会记住这个测试的名字,存储在本地存储。下次运行这个测试的时候,失败的测试将会在所有的测试之前运行。输出的顺序不会被改变,但是执行的顺讯会被改变。因此,Hide passed tests 选项,你仍然看到测试失败的用例,如果它仍然失败,你可以立即处理。
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
.
自动重新排序会自动产生,意味着,你测试必须是独立的,就像前面讨论的哪样。如果你测试不是独立的,你可能会看到一些随机导致的错误。你可以使用正常的方法进行修复。如果你真的不想使用,你可以使用 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.
除了自动排序,有一些手动选项。你可以通过点击Rerun重新运行任何测试。这将添加一个“testnumber = N”参数查询字符串,“N”是测试你的点击数。然后你可以重新载入页面保持运行测试,或使用浏览器的返回按钮,运行所有的测试。
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".
包含在模块里面的测试将被以特定的方式全部运行,除非你在右上方选择特定的模块运行。它将建立一个“模块=N”查询字符串,“N”是模块的编码名称,例如“?module=testEnvironment%20with%20object”。
Further tutorials 更多的教程
- A short QUnit introduction in english
- A short QUnit introduction in german
- Nettuts on Testing with QUnit
- Running QUnit tests with Rhino
- Martin Fowler on Eradicating Non-Determinism in Tests. Not QUnit specific, but very useful advice and a lot of it applies to JavaScript
- ScriptJunkie article on Automating JavaScript Testing with QUnit
- Ben Alman's slides on "Unit Testing with QUnit"
If you want to read more on unit testing JavaScript (not specific to QUnit), check out the book Test-Driven JavaScript Development.
如果你想阅读更多关于JavasScript 单元测试(不仅仅是Qunit),参考书籍 Test-Driven JavaScript Development.
*This section was first published, under a non-exclusive license, as the last chapter in the jQuery Cookbook, authored by Scott González and Jörn Zaefferer. As QUnit changed since the book was printed, this version is more up-to-date.
*这个章节是第一次出版,属于公共署名协议,座位Jquery CookBook的最后一个章节,作者是Scott González 和 Jörn Zaefferer 随着Qunit的修改以及此书被印刷,这个版本是目前最新。