本篇我们将通过对Date.strftime编写单元测试的例子,学会断言、测试用例函数的相关知识。
首先我们先来看Date.strftime的实现代码。
Date.prototype.strftime = (function () { function strftime(format) { var date = this; return (format + "").replace(/%([a-zA-Z])/g, function (m, f) { var formatter = Date.formats && Date.formats[f]; if (typeof formatter == "function") { return formatter.call(Date.formats, date); } else if (typeof formatter == "string") { return date.strftime(formatter); } return f; }); } // 内部帮助函数 function zeroPad(num) { return (+num < 10 ? "0" : "") + num; } Date.formats = { // Formatting 方法 d: function (date) { return zeroPad(date.getDate()); }, m: function (date) { return zeroPad(date.getMonth() + 1); }, y: function (date) { return zeroPad(date.getYear() % 100); }, Y: function (date) { return date.getFullYear(); }, // Format 速记方式 F: "%Y-%m-%d", D: "%m/%d/%y" }; return strftime; }());
Date.prototype.strftime函数整体是一个即时匿名函数,该函数会自动执行,把strftime函数作为结果返回。strftime()通过正则表的式判断格式化标示符,返回正确的结果。 zeroPad()在1-9的数据前加0 。Date.formats 对象提供一系列辅助方法,用来处理时间。
如果代码有哪里看不懂可以给我留言,我会尽量讲解。
断言
单元测试的核心是断言,通过它的自动运行来比较开发者对于系统的预期结果是否和执行结果一致。两者一致则说明我们的系统一切正常,否则就存在问题,需要我们进一步确认。我们先来看一个简单的断言函数:
function assert(message, expr) { if (!expr) { throw new Error(message); } assert.count++; return true; } assert.count = 0;
上面的函数只是简单的验证第二个参数是否是真值(除了false, null, undefined, 0, "", 和 NaN这些值之外的都可以)。断言成功,assert.count会加1,失败则抛出异常。
我们可以使用他来测试strftime() 方法。
var date = new Date(2009, 9, 2); try { assert("%Y should return full year", date.strftime("%Y") === "2009"); assert("%m should return month", date.strftime("%m") === "10"); assert("%d should return date", date.strftime("%d") === "02"); assert("%y should return year as two digits", date.strftime("%y") === "09"); assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02"); console.log(assert.count + " tests OK"); } catch (e) { console.log("Test failed: " + e.message); }
在自动化测试中,我们经常使用绿色代表成功,红色代表失败。我们可以创建一个output方法输出不同颜色的信息条,代替console.log的功能。
function output(text, color) { var p = document.createElement("p"); p.innerHTML = text; p.style.color = color; document.body.appendChild(p); } // 可以用它来代替上面断言例子中的 console.log output(assert.count + " tests OK", "#0c0"); // 失败的时候使用下面的方法: output("Test failed: " + e.message, "#c00");
测试用例函数
在创建断言函数之后,我们需要创建测试用例函数,以便把断言组织起来。测试用例里面包含很多子的测试方法,每个测试方法针对被测对象的一部分行为进行测试。下面我们看一个简单的测试用例函数的实现,其中name是测试用例的名称,tests是一组测试方法,其中每个测试方法以‘test’开头。
function testCase(name, tests) { assert.count = 0; var successful = 0; var testCount = 0;
for (var test in tests) { if (!/^test/.test(test)) { continue; }
testCount++;
try { tests[test](); output(test, "#0c0"); successful++; } catch (e) { output(test + " failed: " + e.message, "#c00"); } }
var color = successful == testCount ? "#0c0" : "#c00"; output("<strong>" + testCount + " tests, " + (testCount - successful) + " failures</strong>", color); }
下面我们使用上面的方法测试strftime()。
var date = new Date(2009, 9, 2); testCase("strftime test", { "test format specifier %Y": function () { assert("%Y should return full year", date.strftime("%Y") === "2009"); }, "test format specifier %m": function () { assert("%m should return month", date.strftime("%m") === "10"); }, "test format specifier %d": function () { assert("%d should return date", date.strftime("%d") === "02"); }, "test format specifier %y": function () { assert("%y should return year as two digits", date.strftime("%y") === "09"); }, "test format shorthand %F": function () { assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02"); } });
这组测试方法针对strftime()不同功能点进行测试,组合起来实现了对其全部功能的测试,最后把测试结果展现给用户。可以说效果还是相当不错的。
Setup 和 Teardown
接下来我们介绍setup()和teardown()方法的实现。这两个方法分别在tests执行之前和之后运行,setup()可以用来初始化测试条件,teardown()可以用来销毁相关条件。下面我们完善之前的testCase()方法,添加对setup和teardown()的支持。
function testCase(name, tests) { assert.count = 0; var successful = 0; var testCount = 0; var hasSetup = typeof tests.setUp == "function"; var hasTeardown = typeof tests.tearDown == "function"; for (var test in tests) { if (!/^test/.test(test)) { continue; } testCount++; try { if (hasSetup) { tests.setUp(); } tests[test](); output(test, "#0c0"); if (hasTeardown) { tests.tearDown(); } successful++; } catch (e) { output(test + " failed: " + e.message, "#c00"); } } var color = successful == testCount ? "#0c0" : "#c00"; output("<strong>" + testCount + " tests, " + (testCount - successful) + " failures</strong>", color); }
下面我们用改进后的testCase()重新测试strftime()方法。
testCase("strftime test", { setUp: function () { this.date = new Date(2009, 9, 2, 22, 14, 45); }, "test format specifier Y": function () { assert("%Y should return full year", this.date.strftime("%Y") == 2009); }, // ... });
总结
本文提供的断言和测试用例函数比较简单,相信大家一看就会,其他复杂的测试框架则要相对复杂的多。但是他们在基本原理上没有太大差别,只是代码实现方式不同,再者就是api提供的更多些。本文主要目地在于抛砖引玉,向大家介绍测试框架中相关方法大致的实现方式,以及如何使用测试框架进行单元测试。要想更好的精通单元测试,除了继续加强理论知识的学习、不断的实践之外,还可以看看其他框架的源代码,相信对你定会有比较大的帮助。