我正准备在一个新项目中开始使用 JavaScript 的TDD。我计划使用QUnit进行单元测试。在编写测试时,可以通过在浏览器中刷新测试页面来运行测试。
为了持续集成(并确保测试在所有浏览器中运行),我将使用Selenium自动加载每个浏览器中的测试,然后读取结果。这些测试将在每次提交到源代码控制时运行。
我还将使用JSCoverage对测试进行代码覆盖分析。这也将通过 Selenium 自动化。
我目前正在设置这个过程。一旦我完成设置,我将更新此答案并提供更详细的内容。
测试工具:
有很多 JavaScript 单元测试框架(JSUnit、scriptaculous 等),但我所知道的唯一一个可以与自动构建一起使用的是 JSUnit。
如果你正在进行“真正”的单元测试,你不应该需要 AJAX 支持。例如,如果你正在使用 RPC Ajax 框架,比如 DWR,你可以很容易地编写一个模拟函数:
function mockFunction(someArg, callback) { var result = ...; // 一些处理 setTimeout(
function() { callback(result); }, 300 // 一些虚假延迟 ); }
是的,JSUnit 确实处理超时:在 JSUnit 测试中模拟时间
我是js-test-driver的忠实粉丝。
它在CI环境中表现良好,并能捕获实际浏览器进行跨浏览器测试。
我刚刚成功地让Hudson CI运行JasmineBDD(无头模式),至少适用于纯JavaScript单元测试。
(Hudson通过shell运行Java,然后运行Envjs,最后运行JasmineBDD。)
不过,我还没有让它与大型库(如prototype)很好地配合起来。
我认为JSUnit正在逐渐失去其影响力。我们刚刚用YUI Test替换了它。
与使用qUnit的示例类似,我们使用Selenium来运行测试。我们将这个测试独立于其他Selenium测试运行,因为它没有正常UI回归测试的依赖项(例如将应用程序部署到服务器)。
首先,我们有一个基本的JavaScript文件,它包含在所有的测试HTML文件中。它处理设置YUI实例、测试运行器、YUI.Test.Suite对象以及Test.Case。它有一些方法可以通过Selenium访问,以运行测试套件,检查测试运行器是否仍在运行(结果在完成后才可用),以及获取测试结果(我们选择JSON格式):
var yui_instance; // The YUI instance
var runner; // The YAHOO.Test.Runner
var Assert; // An instance of YAHOO.Test.Assert to save coding
var testSuite; // The YAHOO.Test.Suite that will get run.
/**
* Sets the required value for the name property on the given template, creates
* and returns a new YUI Test.Case object.
*
* @param template the template object containing all of the tests
*/
function setupTestCase(template) {
template.name = "jsTestCase";
var test_case = new yui_instance.Test.Case(template);
return test_case;
}
/**
* Sets up the test suite with a single test case using the given
* template.
*
* @param template the template object containing all of the tests
*/
function setupTestSuite(template) {
var test_case = setupTestCase(template);
testSuite = new yui_instance.Test.Suite("Bond JS Test Suite");
testSuite.add(test_case);
}
/**
* Runs the YAHOO.Test.Suite
*/
function runTestSuite() {
runner = yui_instance.Test.Runner;
Assert = yui_instance.Assert;
runner.clear();
runner.add(testSuite);
runner.run();
}
/**
* Used to see if the YAHOO.Test.Runner is still running. The
* test results are not available until it is done running.
*/
function isRunning() {
return runner.isRunning();
}
/**
* Gets the results from the YAHOO.Test.Runner
*/
function getTestResults() {
return runner.getResults(yui_instance.Test.Format.JSON);
}
@Parameters
public static List<Object[]> data() throws Exception {
yui_test_codebase = "file:///c://myapppath/yui/tests";
List<Object[]> testResults = new ArrayList<Object[]>();
pageNames = new ArrayList<String>();
pageNames.add("yuiTest1.html");
pageNames.add("yuiTest2.html");
testResults.addAll(runJSTestsInBrowser(IE_NOPROXY));
testResults.addAll(runJSTestsInBrowser(FIREFOX));
return testResults;
}
/**
* Creates a Selenium instance for the given browser, and runs each
* YUI Test page.
*
* @param aBrowser
* @return
*/
private static List<Object[]> runJSTestsInBrowser(Browser aBrowser) {
String yui_test_codebase = "file:///c://myapppath/yui/tests/";
String browser_bot = "this.browserbot.getCurrentWindow()"
List<Object[]> testResults = new ArrayList<Object[]>();
selenium = new DefaultSelenium(APPLICATION_SERVER, REMOTE_CONTROL_PORT, aBrowser.getCommand(), yui_test_codebase);
try {
selenium.start();
/*
* Run the test here
*/
for (String page_name : pageNames) {
selenium.open(yui_test_codebase + page_name);
//Wait for the YAHOO instance to be available
selenium.waitForCondition(browser_bot + ".yui_instance != undefined", "10000");
selenium.getEval("dom=runYUITestSuite(" + browser_bot + ")");
// Output from the tests is not available until
// the YAHOO.Test.Runner is done running the suite
selenium.waitForCondition("!" + browser_bot + ".isRunning()", "10000");
String output = selenium.getEval("dom=getYUITestResults(" + browser_bot + ")");
JSONObject results = JSONObject.fromObject(output);
JSONObject test_case = results.getJSONObject("jsTestCase");
JSONArray testCasePropertyNames = test_case.names();
Iterator itr = testCasePropertyNames.iterator();
/*
* From the output, build an array with the following:
* Test file
* Test name
* status (result)
* message
*/
while(itr.hasNext()) {
String name = (String)itr.next();
if(name.startsWith("test")) {
JSONObject testResult = test_case.getJSONObject(name);
String test_name = testResult.getString("name");
String test_result = testResult.getString("result");
String test_message = testResult.getString("message");
Object[] testResultObject = {aBrowser.getCommand(), page_name, test_name, test_result, test_message};
testResults.add(testResultObject);
}
}
}
} finally {
// If an exception is thrown, this will guarantee that the selenium instance
// is shut down properly
selenium.stop();
selenium = null;
}
return testResults;
}
/**
* Inspects each test result and fails if the testResult was not "pass"
*/
@Test
public void inspectTestResults() {
if(!this.testResult.equalsIgnoreCase("pass")) {
fail(String.format(MESSAGE_FORMAT, this.browser, this.pageName, this.testName, this.message));
}
}
我发布了一个小型库(链接),用于验证依赖于浏览器的JavaScript测试,而无需使用浏览器。这是一个Node.js模块,它使用zombie.js加载测试页面并检查结果。我在我的博客上写了关于它的文章。以下是自动化的样子:
var browsertest = require('../browsertest.js').browsertest;
describe('browser tests', function () {
it('should properly report the result of a mocha test page', function (done) {
browsertest({
url: "file:///home/liam/work/browser-js-testing/tests.html",
callback: function() {
done();
}
});
});
});
有一个新项目可以让你在Java环境(如Ant)中运行QUnit测试,这样你就可以完全将客户端测试套件与其他单元测试集成。
http://qunit-test-runner.googlecode.com
我已经使用它来对jQuery插件、objx代码、自定义面向对象的JavaScript进行单元测试,而且无需修改即可适用于所有内容。