4

我想在 TeamCity 中对 JavaScript 使用单元测试。

我正在使用 QUnit,并且我看到一些建议使用 phantomjs 和 QUnitTeamCityDriver 的地方。我只是无法让它工作......

我在这方面没有很多经验,似乎甚至无法让 phantomjs 在命令行中运行单元测试。

我从字面上复制了 QUnitTeamCityDriver 中的示例:simple_test.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="test_tools/qunit-1.10.0.css">
  <script type="text/javascript" src="resources/jquery-1.8.1.js"></script>
  <script type="text/javascript" src="test_tools/qunit-1.10.0.js"></script>
  <script type="text/javascript" src="qunit_teamcity_driver/QUnitTeamCityDriver.js"></script> 
  <script type="text/javascript" src="tests.js"></script> 
</head>
<body>
  <div id="qunit"></div>
  <h1 id="qunit-header">QUnit example</h1>
  <h2 id="qunit-banner"></h2>
  <div id="qunit-testrunner-toolbar"></div>
  <h2 id="qunit-userAgent"></h2>
  <ol id="qunit-tests"></ol>
  <div id="qunit-fixture">test markup, will be hidden</div>   
</body>
</html>

如果我尝试使用 simple_test.html,tests.js 有一些简单的测试是否有效。

当然,html 中的其他引用文件位于相应的文件夹中。

phantomjs.exe、tests.js 和 simple_test.html 位于我调用的目录的根目录中。

TeamCity 构建的方向是:

Add a "Command Line" Build Step to your build in TeamCity which executes Tests.htm via PhantomJS
Command executable: C:\PhamtomJS\phantomjs.exe
Command parameters: \Scripts\QUnitTeamCityDriver.phantom.js Tests.htm

(这不起作用,所以我想在将命令行放入 TeamCity 之前先在实际命令行上进行测试)

我尝试过的一些事情:

phantomjs.exe tests.js
phantomjs.exe tests.js simple_test.html
phantomjs.exe simple_test.html
phantomjs.exe test_tools\qunit-1.10.0.js tests.js simple_test.html
phantomjs.exe qunit_teamcity_driver/QUnitTeamCityDriver.phantom.js simple_test.html

结果:解析错误或找不到变量:测试

请有人指出我正确的方向,给我一个例子,告诉我我做错了什么?非常感谢。

4

3 回答 3

6

如果您的技术堆栈是匹配的,我已经成功使用Chutzpah

除其他外,它通过处理 kabaros 描述的对 phantomjs 的调用以及提供 TeamCity 和 Visual Studio 集成来为您完成繁重的工作。

于 2012-10-19T21:27:14.873 回答
2

简短的回答是,您需要运行:

phantomjs.exe script-runner.js simple_test.html

长答案:

您将需要一个 javascript 文件来调用您的测试,我写了很多变体,但我发现最好的一个是 QUnitTeamCityDriver https://github.com/redbadger/QUnitTeamCityDriver。即使我不与 TeamCity 合作,我也使用了一些小改动。

这是文件的内容,可能有一些更改(我在最近的一个项目中使用过它),但你应该在 github 上找到原始文件(称之为 script-runner.js 并放在与 phantom 相同的文件夹中.exe 为方便起见):

function waitFor(testFx, onReady, timeOutMillis) {
    var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s
        start = new Date().getTime(),
        condition = false,
        interval = setInterval(function () {
            if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
                // If not time-out yet and condition not yet fulfilled
                condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
            } else {
                if (!condition) {
                    // If condition still not fulfilled (timeout but condition is 'false')
                    console.log("'waitFor()' timeout");
                    phantom.exit(1);
                } else {
                    // Condition fulfilled (timeout and/or condition is 'true')
                    typeof (onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
                    clearInterval(interval); //< Stop this interval
                }
            }
        }, 100); //< repeat check every 250ms
};

var page = new WebPage();

// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
page.onConsoleMessage = function (msg) {
    console.log(msg);
};

page.open(phantom.args[0], function (status) {

    if (status !== "success") {
        console.log("Unable to access network");
        phantom.exit(1);
    } else {
        waitFor(function () {
            return page.evaluate(function () {
                var el = document.getElementById('qunit-testresult');
                if (el && el.innerText.match('completed')) {
                    return true;
                }
                return false;
            });
        }, function () {
            phantom.exit();
        }, 10000);
    }
});

这基本上会打开你的 html 页面并解析它。这对我来说是一个令人困惑的部分,phantomjs 最终是一个浏览器,你应该将这个脚本传递给它,最终将打开一个 HTML 页面而不是 Javascript 文件。所以你像这样调用phantomjs:

phantomjs.exe script-runner.js [path to file containing tests]

所以在你的情况下,它将是:

phantomjs.exe script-runner.js simple_test.html

难题的最后一点是,在包含测试的 html 页面中,您应该添加对 javascript 文件的引用,即引用中名为 QUnitTeamCityDriver.js 的文件。这将连接到 qUnit 事件并将它们引导到幻象,以决定什么是通过的测试,什么是失败的测试。文件的内容(同样你可以在 github 的项目页面上找到更好的最新版本):

/*
Reference this file in the html files of the QUnit tests
Based on the work on this team city driver: https://github.com/redbadger/QUnitTeamCityDriver/
*/
if (navigator.userAgent.indexOf("PhantomJS") !== -1) {
    String.prototype.format = function () {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function (match, number) {
            return typeof args[number] != 'undefined'
      ? args[number]
      : '{' + number + '}';
        });
    };

    var suiteName = "QUnit Tests";
    var currentTestName = "";
    var hasBegun = false;

    qunitBegin = function () {
        console.log("[testSuiteStarted name='{0}']".format(suiteName));
    };

    /* QUnit.testStart({ name }) */
    QUnit.testStart = function (args) {
        if (!hasBegun) {
            qunitBegin();
            hasBegun = true;
        }
        currentTestName = args.name;
    };

    QUnit.moduleStart = function (args) {
        console.log("Module started: {0}".format(args.name));
    };

    /* QUnit.log({ result, actual, expected, message }) */
    QUnit.log = function (args) {

        var currentAssertion = "{0} > {1}".format(currentTestName, args.message);

        //console.log("[testStarted name='{0}']".format(currentAssertion));

        if (!args.result) {
            console.log("**[testFailed] type='comparisonFailure' name='{0}' details='expected={1}, actual={2}' expected='{1}' actual='{2}'".format(currentAssertion, args.expected, args.actual));
        }

        console.log("[testFinished] name='{0}'".format(currentAssertion));
    };

    /* QUnit.done({ failed, passed, total, runtime }) */
    QUnit.done = function (args) {
        console.log("[testSuiteFinished name='{0}']".format(suiteName));
    };
}

这基本上与 QUnit 定义的回调挂钩,并将消息发送到由 phantomjs 显示的控制台。QUnit 回调文档可在此处找到:http: //api.qunitjs.com/category/callbacks/

至于与 TeamCity 挂钩,它可能只是构建步骤定义中的一个设置,用于检查输出中的某个字符串(例如,输出中存在“[testFailed]”表示失败),我已经完成了CruiseControl 并围绕它创建了一个 Nunit 包装器。最后,同样的概念适用,您只需解析 phatomjs 输出以检查是否存在 [testFailed] 或任何指示失败的字符串。

于 2012-10-19T20:55:48.193 回答
2

除了上面提供的建议外,我建议使用 qunit 提供的新 phantomjs qunit runner:

https://github.com/jquery/qunit/tree/master/addons/phantomjs

如果您有错误的异步测试,请查看我的拉取请求:

https://github.com/jquery/qunit/pull/415

于 2013-02-19T17:46:50.983 回答