使用 intern 編寫測試程式碼
When writing JavaScript tests, it is common to write them according to a specific testing API that defines Suites, Tests, and other aspects of testing infrastructure. These testing APIs are known as test interfaces. Intern currently comes with support for 3 different test interfaces: TDD, BDD, and object. Internally, all interfaces generate the same testing structures, so you can use whichever interface you feel matches your preference and coding style. Examples of each tests using each of these interfaces can be found below.
Assertions
A test needs a way to verify some logic about the target being tested, such as whether or not a given variable is truthy. This is known as an assertion, and forms the basis for software testing. Intern supports extensible assertions via the Chai Assertion Library. The assert and expect/should assertion interfaces are exposed via the following modules, and should be required and used in your tests:
intern/chai!assert intern/chai!expect intern/chai!should As of Intern 2, it is also possible to use the Chai as Promised plugin when writing functional tests by loading the Chai as Promised plugin into Chai:
define([ 'intern/chai!', 'chai-as-promised' ], function (chai, chaiAsPromised) { chai.use(chaiAsPromise); chai.should(); }); Asynchronous testing
Asynchronous testing in Intern is based on promises. You may either return a promise from a test function (convenient for interfaces that already support promises) or call this.async from within a test function to enable asynchronous testing.
Returning a promise
If your test returns a promise (any object with a then function), it is understood that your test is asynchronous. Resolving the promise indicates a passing test, and rejecting the promise indicates a failed test. The test will also fail if the promise is not fulfilled within the timeout of the test (the default is 30 seconds; set this.timeout to change the value).
Calling this.async()
All tests have an async method that can be used to get a Deferred object for asynchronous testing. After calling this method, Intern will assume your test is asynchronous, even if you do not return a promise from your test function. (If you do return a promise, the returned promise takes precedence over the promise generated by this.async.)
async returns a Deferred object that can be used to resolve the test once it has completed. In addition to the standard Deferred API, this Deferred object has two additional methods:
Deferred#callback(function):function: This method wraps a callback and returns a function that will resolve the Deferred automatically when the function is called, so long as the callback does not throw any errors. If the callback throws an error, the Deferred will be rejected with that error instead. This is the most common way to complete an asynchronous test. Deferred#rejectOnError(function):function: This method works similarly to Deferred#callback, except it only implements rejection behavior. In other words, the newly created function will reject the Deferred when there is an error, but will not resolve it when there is no error. This is useful when working with nested callbacks where only the innermost callback should resolve the Deferred but a failure in any of the outer callbacks should reject it. The async method accepts two optional arguments. The first argument, timeout, will set the timeout of your test in milliseconds. If not provided, this defaults to 30 seconds. The second argument, numCallsUntilResolution, specifies how many times dfd.callback should be called before actually resolving the promise. This defaults to 1. numCallsUntilResolution is useful in rare cases where you may have a callback that will be called several times and the test should be considered complete only on the last invocation.
A basic asynchronous test using this.async looks like this (object style):
define([ 'intern!object', 'intern/chai!assert', 'request' ], function (registerSuite, assert, request) { registerSuite({ name: 'async demo',
'async test': function () {
var dfd = this.async(1000);
request('http://example.com/test.json').then(dfd.callback(function (data) {
assert.strictEqual(data, 'Hello world!');
}), dfd.reject.bind(dfd));
}
});
}); In this example, an XHR call is performed. When the call is completed successfully, the data is checked to make sure it is correct. If the data is correct, dfd will be resolved; otherwise, it will be rejected (because assert.strictEqual will throw an error). If the call fails, dfd.reject is called.
Functional testing
In addition to regular unit tests, Intern supports a type of testing that can simulate user interaction with DOM elements, known as functional testing. Functional tests are slightly different from normal unit tests because they are executed remotely from the test runner, whereas unit tests are executed directly on the browser under test. In a functional test, a remote object is exposed that has methods for interacting with a remote browser environment. The general flow of a functional test should be as follows:
- Load an HTML page into the remote environment
Because the test code isn’t exposed to this remote environment at all, this HTML page should include script tags for all necessary JavaScript. Note that if a functional test needs to explicitly wait for certain widgets or elements on this HTML page to be rendered (or some other condition) before proceeding, the pollUntil promise helper can be used. This helper waits until a global variable is non-null before continuing with execution, and errors out if an optional timeout is exceeded.
this.remote .get(require.toUrl('./SomeTest.html')) .then(pollUntil('return window.ready;', 5000)); 2. Use the methods available on the remote object to interact with the remote context.
The remote object is a Leadfoot Command object.
this.remote .get(require.toUrl('./fixture.html')) .findById('operation') .click() .type('hello, world') .end() 3. Make assertions just like regular unit testing.
Just like unit tests, functional tests support extensible assertions via the Chai Assertion Library. The assert and expect/should assertion interfaces are exposed via the intern/chai!assert, intern/chai!expect, and intern/chai!should modules. See the full Chai API documentation for more information.
this.remote .setFindTimeout(10000) .findById('result') .text() .then(function (resultText) { assert.equal(resultText, 'hello world', 'When form is submitted, operation should complete successfully'); }); Testing non-AMD code
CommonJS code
CommonJS code, including Node.js built-ins, can be loaded as an AMD dependency from within Node.js using the dojo/node AMD plugin that comes with Intern:
define([ 'intern!object', 'intern/chai!assert', 'intern/dojo/node!path' ], function (registerSuite, assert, path) { registerSuite({ name: 'path',
'basic tests': function () {
path.join('a', 'b');
// ...
}
});
}); Browser code
If you are attempting to test non-AMD code that is split across multiple JavaScript files which must be loaded in a specific order, use the intern/order plugin instead of specifying those files as direct dependencies in order to ensure they load correctly:
define([ 'intern!object', 'intern/chai!assert', 'intern/order!../jquery.js', 'intern/order!../plugin.jquery.js' ], function (registerSuite, assert) { registerSuite({ name: 'plugin.jquery.js',
'basic tests': function () {
jQuery('<div>').plugin();
// ...
}
});
}); You can also try use.js.
(Of course, it is strongly recommended that you upgrade your code to use AMD so that this is not necessary.)
Example Tests
BDD
define([ 'intern!bdd', 'intern/chai!expect', '../Request' ], function (bdd, expect, Request) { with (bdd) { describe('demo', function () { var request, url = 'https://github.com/theintern/intern';
// before the suite starts
before(function () {
request = new Request();
});
// before each test executes
beforeEach(function () {
request.reset();
});
// after the suite is done
after(function () {
request.cleanup();
});
// multiple methods can be registered and will be executed in order
// of registration
after(function () {
if (!request.cleaned) {
throw new Error(
'Request should have been cleaned up after suite execution.');
}
// these methods can be made asynchronous as well by returning
// a promise
});
// asynchronous test for Promises/A-based interfaces
it('should demonstrate a Promises/A-based asynchronous test', function () {
// `getUrl` returns a promise
return request.getUrl(url).then(function (result) {
expect(result.url).to.equal(url);
expect(result.data.indexOf('next-generation') > -1).to.be.true;
});
});
// asynchronous test for callback-based interfaces
it('should demonstrate a callback-based asynchronous test', function () {
// test will time out after 1 second
var dfd = this.async(1000);
// dfd.callback resolves the promise as long as no errors are
// thrown from within the callback function
request.getUrlCallback(url, dfd.callback(function () {
expect(result.url).to.equal(url);
expect(result.data.indexOf('next-generation') > -1).to.be.true;
});
// no need to return the promise; calling `async` makes the test async
});
// nested suites work too
describe('xhr', function () {
// synchronous test
it('should run a synchronous test', function () {
expect(request.xhr).to.exist;
});
});
});
}
}); TDD
define([ 'intern!tdd', 'intern/chai!assert', '../Request' ], function (tdd, assert, Request) { with (tdd) { suite('demo', function () { var request, url = 'https://github.com/theintern/intern';
// before the suite starts
before(function () {
request = new Request();
});
// before each test executes
beforeEach(function () {
request.reset();
});
// after the suite is done
after(function () {
request.cleanup();
});
// multiple methods can be registered and will be executed in order
// of registration
after(function () {
if (!request.cleaned) {
throw new Error(
'Request should have been cleaned up after suite execution.');
}
// these methods can be made asynchronous as well by returning
// a promise
});
// asynchronous test for Promises/A-based interfaces
test('#getUrl (async)', function () {
// `getUrl` returns a promise
return request.getUrl(url).then(function (result) {
assert.equal(result.url, url,
'Result URL should be requested URL');
assert.isTrue(result.data.indexOf('next-generation') > -1,
'Result data should contain term "next-generation"');
});
});
// asynchronous test for callback-based interfaces
test('#getUrlCallback (async)', function () {
// test will time out after 1 second
var dfd = this.async(1000);
// dfd.callback resolves the promise as long as no errors are
// thrown from within the callback function
request.getUrlCallback(url, dfd.callback(function () {
assert.equal(result.url, url,
'Result URL should be requested URL');
assert.isTrue(result.data.indexOf('next-generation') > -1,
'Result data should contain term "next-generation"');
});
// no need to return the promise; calling `async` makes the
// test async
});
// nested suites work too
suite('xhr', function () {
// synchronous test
test('sanity check', function () {
assert.ok(request.xhr,
'XHR interface should exist on `xhr` property');
});
});
});
}
}); Object
define([ 'intern!object', 'intern/chai!assert', '../Request' ], function (registerSuite, assert, Request) { var request, url = 'https://github.com/theintern/intern';
registerSuite({
name: 'demo',
// before the suite starts
setup: function () {
request = new Request();
},
// before each test executes
beforeEach: function () {
request.reset();
},
// after the suite is done
teardown: function () {
request.cleanup();
if (!request.cleaned) {
throw new Error(
'Request should have been cleaned up after suite execution.');
}
},
// asynchronous test for Promises/A-based interfaces
'#getUrl (async)': function () {
// `getUrl` returns a promise
return request.getUrl(url).then(function (result) {
assert.equal(result.url, url,
'Result URL should be requested URL');
assert.isTrue(result.data.indexOf('next-generation') > -1,
'Result data should contain term "next-generation"');
});
},
// asynchronous test for callback-based interfaces
'#getUrlCallback (async)': function () {
// test will time out after 1 second
var dfd = this.async(1000);
// dfd.callback resolves the promise as long as no errors are
// thrown from within the callback function
request.getUrlCallback(url, dfd.callback(function () {
assert.equal(result.url, url,
'Result URL should be requested URL');
assert.isTrue(result.data.indexOf('next-generation') > -1,
'Result data should contain term "next-generation"');
});
// no need to return the promise; calling `async` makes the test
// async
},
// nested suites work too
'xhr': {
// synchronous test
'sanity check': function () {
assert.ok(request.xhr,
'XHR interface should exist on `xhr` property');
}
}
});
}); Functional
define([ 'intern!object', 'intern/chai!assert', '../Request', 'require' ], function (registerSuite, assert, Request, require) { var request, url = 'https://github.com/theintern/intern';
registerSuite({
name: 'demo',
'submit form': function () {
return this.remote
.get(require.toUrl('./fixture.html'))
.findById('operation')
.click()
.type('hello, world')
.end()
.findById('submit')
.click()
.end()
.setFindTimeout(Infinity)
.findById('result')
.setFindTimeout(0)
.text()
.then(function (resultText) {
assert.ok(resultText.indexOf(
'"hello, world" completed successfully') > -1,
'On form submission, operation should complete successfully');
});
}
});
});
相關文章
- 使用 xunit 編寫測試程式碼
- 前端進階-編寫測試程式碼前端
- 編寫可測試的 JavaSript 程式碼Java
- 編寫可測試的 JavaScript 程式碼JavaScript
- 如何編寫優秀的測試程式碼|單元測試
- 程式碼寫作測試
- 無需編寫程式碼,API業務流程測試,零程式碼實現API
- 為程式碼編寫穩定的單元測試 [Go]Go
- 編寫難於測試的程式碼的5種方式
- 寫 Laravel 測試程式碼 (五)Laravel
- 寫 Laravel 測試程式碼 (二)Laravel
- 你們現在編寫程式碼是從先寫測試開始嗎?
- 如何用 JMeter 編寫效能測試指令碼?JMeter指令碼
- 效能測試——壓測工具locust——指令碼初步編寫指令碼
- Golang 編寫測試教程Golang
- .NET Core TDD 前傳: 編寫易於測試的程式碼 -- 縫
- 介面自動化測試之PHPUnit-框架程式碼編寫2PHP框架
- Laravel 單元測試實戰(2)- 編寫實際功能並讓程式碼測試透過Laravel
- 【轉】測試用例編寫(功能測試框架)框架
- 編寫可測試的Javascript程式碼(1):反模式及其解決方案JavaScript模式
- 使用C#winform編寫滲透測試工具--SQL隱碼攻擊C#ORMSQL
- Java編碼測試Java
- 使用Python編寫一個滲透測試探測工具Python
- 如何編寫測試團隊通用的Jmeter指令碼JMeter指令碼
- 開發測試用例:手動擼程式碼 VS 填鴨式編寫
- .NET Core TDD 前傳: 編寫易於測試的程式碼 -- 依賴項
- .NET Core TDD 前傳: 編寫易於測試的程式碼 -- 構建物件物件
- 編寫可測試的Javascript程式碼(2):從反模式進行重構JavaScript模式
- 不用寫程式碼,也能做好介面測試
- 使用 Source Generators 快速編寫 MVVM 程式碼MVVM
- 效能測試報告編寫技巧測試報告
- JUnit5編寫基本測試
- 如何編寫功能測試報告測試報告
- Laravel 單元測試實戰(3)- 編寫整合測試確保介面和資料庫程式碼正確Laravel資料庫
- 用LoadRunner編寫socket應用的測試指令碼指令碼
- 介面測試用例編寫和測試關注點
- 軟體測試員必須編寫程式碼嗎?掌握多少程式設計能力才夠?程式設計
- .NET Core TDD 前傳: 編寫易於測試的程式碼 -- 單一職責