使用JavaScript进行自动化的单元测试
我正在尝试将一些JavaScript单元测试合并到我的自动构建过程中。当前,JSUnit与JUnit可以很好地协同工作,但是它似乎已经废弃了,并且对AJAX,调试和超时缺乏良好的支持。
有没有人有运气(使用ANT)自动化单元测试库,例如YUI测试,JQuery的QUnit或者jQUnit(http://code.google.com/p/jqunit/)?
注意:我使用定制的AJAX库,所以Dojo的DOH的问题在于它要求我们使用其自己的AJAX函数调用和事件处理程序来进行任何AJAX单元测试。
解决方案
看看YUITest
有很多javascript单元测试框架(jsUnit,scriptaculous等),但是jsUnit是我所知道的唯一可以与自动构建一起使用的框架。
如果我们要进行"真实"的单元测试,则不需要AJAX支持。例如,如果我们使用的是诸如DWR之类的RPC ajax框架,则可以轻松编写一个模拟函数:
function mockFunction(someArg, callback) {
var result = ...; // some treatments
setTimeout(
function() { callback(result); },
300 // some fake latency
);
}
是的,JsUnit可以处理超时:在jsUnit测试中模拟时间
可以与Ant一起运行的另一个JS测试框架是CrossCheck。在项目的构建文件中有一个通过Ant运行CrossCheck的示例。
CrossCheck尝试模拟浏览器,但收效甚微,其中包括XMLHttpRequest和超时/间隔的模拟样式实现。
不过,它目前无法处理从网页中加载javascript。我们必须指定要加载和测试的javascript文件。如果我们将所有JS与HTML分开,则可能对我们有用。
我即将开始在我正在从事的新项目中执行Javascript TDD。我当前的计划是使用qunit进行单元测试。在开发测试时,只需在浏览器中刷新测试页面即可运行测试。
为了进行持续集成(并确保测试在所有浏览器中运行),我将使用Selenium在每个浏览器中自动加载测试工具,并读取结果。这些测试将在每次签到源代码控制时运行。
我还将使用JSCoverage获得测试的代码覆盖率分析。 Selenium也将自动执行此操作。
我目前正在进行此设置。一旦设置完成,我将使用更准确的详细信息更新此答案。
测试工具:
- 单位
- JSCoverage
- 硒
最近,我读了Bruno的文章,该文章使用JsUnit并在此之上创建了一个JsMock框架...非常有趣。我正在考虑使用他的工作来开始对Javascript代码进行单元测试。
模拟JavaScript或者如何在浏览器环境之外进行单元测试Java
我是js-test-driver的忠实粉丝
它在CI环境中运行良好,并且能够捕获实际的浏览器以进行跨浏览器测试。
我同意jsunit快死了。我们刚刚完成了将其替换为YUI测试。
与使用qUnit的示例类似,我们使用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);
}
至于硒方面,我们使用了参数化测试。我们使用数据方法在IE和FireFox中运行测试,将测试结果解析为Object数组列表,每个数组包含浏览器名称,测试文件名称,测试名称,结果(通过,失败或者忽略)和消息。
实际测试只是断言测试结果。如果不等于"通过",则它将通过YUI测试结果返回的消息使测试失败。
@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));
}
}
我希望这是有帮助的。
我只是让Hudson CI运行JasmineBDD(无头),至少用于纯JavaScript单元测试。
(Hudson通过外壳运行Java,运行Envjs,运行JasmineBDD。)
不过,我还没有像大型原型库那样在大型图书馆中发挥出色的作用。
有一个新项目,可让我们在Java环境(如ant)中运行qunit测试,以便将客户端测试套件与其他单元测试完全集成。
http://qunit-test-runner.googlecode.com
我已经用它来对jQuery插件,objx代码,自定义OO JavaScript进行单元测试,并且无需修改即可用于所有内容。

