使用JavaScript进行自动化的单元测试

时间:2020-03-06 14:23:07  来源:igfitidea点击:

我正在尝试将一些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进行单元测试,并且无需修改即可用于所有内容。