JavaScript自动化单元测试

62

我正试图将一些JavaScript单元测试纳入我的自动化构建流程中。目前JSUnit与JUnit配合良好,但它似乎已被放弃,缺乏对Ajax、调试和超时的良好支持。

有人能够成功地自动化(使用Ant)像YUI测试、jQuery的QUnitjQUnit这样的单元测试库吗?

注意:我使用一个自定义构建的Ajax库,因此Dojo的DOH存在问题,即它要求您使用他们自己的Ajax函数调用和事件处理程序来处理任何Ajax单元测试。


1
相关问题:https://dev59.com/onVC5IYBdhLWcg3wbQbA - Jacques Bosch
14个回答

25

我正准备在一个新项目中开始使用 JavaScript 的TDD。我计划使用QUnit进行单元测试。在编写测试时,可以通过在浏览器中刷新测试页面来运行测试。

为了持续集成(并确保测试在所有浏览器中运行),我将使用Selenium自动加载每个浏览器中的测试,然后读取结果。这些测试将在每次提交到源代码控制时运行。

我还将使用JSCoverage对测试进行代码覆盖分析。这也将通过 Selenium 自动化。

我目前正在设置这个过程。一旦我完成设置,我将更新此答案并提供更详细的内容。


测试工具:


你有没有设置成功?进展如何? - El Yobo
4
我已经完成了这项设定,但使用了稍微不同的技术。我使用JS Test Driver来在每个浏览器中运行单元测试(而不是Selenium),并使用QUnit适配器(http://code.google.com/p/js-test-driver/wiki/QUnitAdapter)。在我的当前雇主那里,我正在使用Jasmine测试框架,并在Jasmine Node(http://github.com/mhevery/jasmine-node)中运行测试,这避免了使用浏览器时的延迟。我使用自己的小型Ruby项目(http://github.com/karl/loris)在每次更改时运行测试。 - Karl
这几乎是Selenium最好的用法。天啊,我浪费了很多时间在那个东西上。咕哝咕哝... - Randy L
嗨@Karl。我也发现qunit是一个有用的工具,但不知道应该测试什么。我有一个用JavaScript编写的带有组合和动画的游戏逻辑。你能给我提供一些信息吗?谢谢。 - Anahit Ghazaryan

21

有很多 JavaScript 单元测试框架(JSUnit、scriptaculous 等),但我所知道的唯一一个可以与自动构建一起使用的是 JSUnit。

如果你正在进行“真正”的单元测试,你不应该需要 AJAX 支持。例如,如果你正在使用 RPC Ajax 框架,比如 DWR,你可以很容易地编写一个模拟函数:

   function mockFunction(someArg, callback) {
      var result = ...; // 一些处理
      setTimeout(
function() { callback(result); }, 300 // 一些虚假延迟 ); }

是的,JSUnit 确实处理超时:在 JSUnit 测试中模拟时间


14

我是js-test-driver的忠实粉丝。

它在CI环境中表现良好,并能捕获实际浏览器进行跨浏览器测试。


1
我喜欢它是因为它与CI集成,但我认为它最大的优点是它可以与YUITest和QUnit一起使用! - AutomatedTester

5

我刚刚成功地让Hudson CI运行JasmineBDD(无头模式),至少适用于纯JavaScript单元测试。

(Hudson通过shell运行Java,然后运行Envjs,最后运行JasmineBDD。)

不过,我还没有让它与大型库(如prototype)很好地配合起来。


5

2

我认为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);
}

关于Selenium方面的事情,我们使用了参数化测试。在数据方法中运行我们的测试,在Internet Explorer和Firefox中运行我们的测试,并将测试结果解析为一个对象数组列表,每个数组包含浏览器名称、测试文件名、测试名称、结果(通过、失败或忽略)和消息。
实际测试只是断言测试结果。如果它不等于“通过”,则用从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));
    }
}

2

1

我发布了一个小型库(链接),用于验证依赖于浏览器的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();
            }
        });
    });

});

1

有一个新项目可以让你在Java环境(如Ant)中运行QUnit测试,这样你就可以完全将客户端测试套件与其他单元测试集成。

http://qunit-test-runner.googlecode.com

我已经使用它来对jQuery插件、objx代码、自定义面向对象的JavaScript进行单元测试,而且无需修改即可适用于所有内容。


1

我查看了您提问的日期,那时有一些不错的JavaScript测试库和框架。

如今,您可以找到更多不同重点的内容,例如TDDBDD、断言以及带/不带运行器支持。

在这个游戏中有很多参与者,例如MochaChaiQUnitJasmine等等...

您可以在this博客中找到更多关于JavaScript、移动和Web测试的信息...


什么是“断言”? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接