我有一个像这样的JS文件中的代码(当然简化):
$(function () {
var num;
$.getJSON('./getNumber.php', function (n) {
num = n;
});
$('#id').on('click', function () { alert(num); });
});
我需要编写两个单元测试:
./getNumber.php
请求#id
时,num
会收到提醒。显然我需要FakeXmlHTTPRequest并模拟alert
函数,甚至可能窥探$.getJSON
。但是我不确定在保持两个原子的同时编写测试的正确方法是什么。
我认为唯一这样做会为每次测试动态注入<script>
块;但我觉得这不对。什么是正确的方法?感谢。
编辑:根据SO之外的评论,我需要学习的是编写可测试的Javascript,而不是尝试为测试用例提供低测试性的测试用例。如果有人能给我一些关于重写这段代码的建议,也会非常感激。
答案 0 :(得分: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);
});
对于这个简单的代码来说,这个例子显然有些过分,但它应该会给你一些好的想法。
您的核心功能现已封装在模块中。您在模块中保留了其他私有函数和变量(如num
),但现在可以测试通过返回对象公开公开的所有内容。 (见下面的注释)
我已将模块外的事件连接起来。如果您的模块很大,您甚至可能希望将此代码放在另一个脚本中。无论哪种方式,它都会以类似MVC的方式将您的关注点分开。你可以测试这段代码,但你真的不应该这样做;它应该是非常简单的jQuery,并且围绕它进行包装测试只不过是测试jQuery库,jQuery库已经过jQuery团队的全面测试。
这可能看起来很奇怪,但不要在模块中使用任何jQuery选择器!如果您的模块需要一个元素,请将其作为函数参数传递。有一些方法可以将这些东西排除在外,但这真的很麻烦,如果你已经正确地分离了你的顾虑,那就没有必要了。我的大部分模块(或其中的对象)都采用init()
方法,我可以传入内容。 - 您可能仍然选择在模块内的元素上使用jQuery的.find()
方法。这只是一个判断调用,只有你可以根据模块的特殊需求做出。这样做会要求你在测试中做更详细的DOM元素模拟,但有时候只需要将几十个元素传递到init()
(只需传入主容器)。
注意:我强烈建议使用RequireJS或其他AMD模块加载程序来管理模块。这将使myModule
之类的内容远离全局命名空间。但是设置QUnit需要有点不耐烦,一旦你掌握了单元测试,可能会遇到一个问题。