3

我在一个 JS 文件中有这样的代码(当然是简化的):

$(function () {
  var num;
  $.getJSON('./getNumber.php', function (n) {
    num = n;
  });

  $('#id').on('click', function () { alert(num); });
});

我需要编写两个单元测试:

  1. 确保脚本在页面加载时向服务器发送./getNumber.php请求
  2. 确保#id单击时num会收到警报。

显然我需要 FakeXmlHTTPRequest 并模拟该alert函数,甚至可以监视$.getJSON. 但是我不确定在保持两个原子的同时编写测试的正确方法是什么。

我认为这样做的唯一方法是<script>为每个测试动态注入块;但我只是觉得那是不对的。什么是正确的方法?谢谢。

编辑:基于 SO 之外的评论,我需要学习的是编写可测试的 Javascript,而不是尝试为低可测试性的东西提出测试用例。如果有人可以给我一些关于重写此代码的建议,我将不胜感激。

4

1 回答 1

2

你的问题没有一个正确的答案,而是有很多好的原则可供学习。考虑以下代码。我已经重构了你的例子。我将在下面解释我所做的一切。

// Define your module in a self-executing function.
// This module is now completely unit testable.
var myModule = (function () {
    var num;

    function getNum() {
        $.getJSON('./getNumber.php', function (n) {
            num = n;
        });
    }

    function alertNum() {
        alert(num);
    }

    // this returned object will by set to myModule and will publicly
    // expose the necessary functions
    return {
        getNum: getNum,
        alertNum: alertNum
    };
})();

// wire up your events
$(function() {
    $("#id").on('click', myModule.alertNum);
});

这个例子对于这个简单的代码来说显然是多余的,但它应该给你一些好主意。

  1. 您的核心功能现在封装在一个模块中。您在模块中保留了额外的私有函数和变量(如num),但是现在可以测试通过返回的对象公开公开的所有内容。(见下面的注释)

  2. 我已经连接了模块外的事件。如果您的模块很大,您甚至可能希望将此代码放在另一个脚本中。无论哪种方式,它都会以更类似于 MVC 的方法分离您的关注点。您可以测试这段代码,但实际上不必这样做;它应该是非常简单的 jQuery,并且围绕它进行包装测试只不过是测试 jQuery 库,它已经被 jQuery 团队彻底测试过。

  3. 这可能看起来很奇怪,但不要在你的模块中使用任何 jQuery 选择器!如果您的模块需要一个元素,请将其作为函数参数传入。有一些方法可以将这些东西排除在外,但这确实很麻烦,如果你已经正确地分离了你的关注点,那就没有必要了。我的大多数模块(或其中的对象)最终都带有一种init()我可以传入东西的方法。-- 你可能仍然选择.find()在你的模块中的元素上使用 jQuery 的方法。这更像是一个判断电话,只有您可以根据您的模块的特定需求做出判断。这样做将要求您在测试中对 DOM 元素进行更详细的模拟,但有时它比传入几十个元素更容易init()(只需传入主容器)。


注意:我强烈建议使用RequireJS或其他 AMD 模块加载器来管理您的模块。这将使myModule上述内容远离全局命名空间。不过,使用 QUnit 设置 Require 有点小题大做,一旦您掌握了单元测试,这可能是一个稍后需要解决的问题。

于 2013-01-09T01:25:56.830 回答