使用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进行单元测试,并且无需修改即可用于所有内容。