在《JavaScript忍者秘籍》2.4测试条件基础知识中,做者给出了一个精简版的assert和assert组的实现,对于初学者而言,这无疑是一个很好的例子,既让咱们获得了一个好用的小工具,又让咱们看到了用javascript实现这个工具是如此的简单。javascript
这里主要是从代码角度最2.4章节作一些补充和说明,包括原有代码中的一些bug及其修正。固然了,既然涉及到了代码解析,这就不能说是初学者的范畴了,至少要多javascript中的函数声明,函数实现,函数闭包等内容有了基本的了解后,才能看懂这篇文章。css
先来讲说assert,应用代码是这个样子的:html
<script type="text/javascript"> assert(1 + 1 === 2, "1 + 1 = 2"); assert(1 + 1 === 3, "1 + 1 = 3"); </script>
assert就是一个javascript函数,有两个参数,第一个参数用来判断表达式是true或false,第二个参数用来对测试作一些说明,测试结果直接显示在html中,这里的测试结果是这个样子的:java
还挺酷的吧。好了,那么咱们就来看看如何实现这个assert?闭包
首先新建一个html文件app
而后在body中加入一个id为rusults的ul节点:ide
<body> <ul id="results"></ul> </body>
后面全部的assert结果列表都要加到这个节点下。函数
assert执行完成后的html结果是这样的:工具
看起来挺简单的吧,就是在ul节点下对每一个assert测试用li节点来表现。对于测试为true的li节点的class被赋值为pass,对于测试为false的li节点的class被赋值为fail。学习
原理清楚了,那么这个assert函数的代码看起来就不复杂了:
function assert(value, desc) { // 建立li节点 var li = document.createElement("li"); // 若是value为true,li的class为pass // 若是value为false,li的class为fail li.className = value ? "pass" : "fail"; // 根据desc建立text节点,而后添加到li节点下 li.appendChild(document.createTextNode(desc)); // 找到document中id为results的节点元素,就是那个body下的ul, // 而后把新建的li节点添加到ul节点下 document.getElementById("results").appendChild(li); }
剩下的就是添加一些css,美化一下html了,既然已经学习javascript了,通常的html和css的内容就不在这说了,完整的html以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>assert</title> <style> body { font-family: sans-serif; font-size: 12pt; } #results { background-color: #e0e0e0; border-radius: 1em; padding: 1em; list-style-position: inside; } ul { list-style-type : circle; } #results li { margin-bottom: 0.2em; } #results li.fail { color: red; text-decoration: line-through; } #results li.pass { color: green; } </style> <script type="text/javascript"> function assert(value, desc) { // 建立li节点 var li = document.createElement("li"); // 若是value为true,li的class为pass // 若是value为false,li的class为fail li.className = value ? "pass" : "fail"; // 根据desc建立text节点,而后添加到li节点下 li.appendChild(document.createTextNode(desc)); // 找到document中id为results的节点元素,就是那个body下的ul, // 而后把新建的li节点添加到ul节点下 document.getElementById("results").appendChild(li); } </script> </head> <body> <ul id="results"></ul> <script type="text/javascript"> assert(1 + 1 === 2, "1 + 1 = 2"); assert(1 + 1 === 3, "1 + 1 = 3"); </script> </body> </html>
asserts表明一个测试组,应用代码是这个样子的:
<script type="text/javascript"> asserts(); test("数值计算测试", function() { assert(1 + 1 === 2, "1 + 1 = 2"); }); test("字符串测试", function() { assert("a" + "b" === "ab", '"a" + "b" = "ab"'); assert("a" + "b" === "abc", '"a" + "b" = "abc"'); }); </script>
这段代码是说,先建立一个描述为“数值计算测试”的测试组,里面加一组assert;再建立一个描述为“字符串测试”的测试组,里面加一组assert。两个测试组之间是平级的关系。
每一个测试组里的一组assert都是不一样的,所以须要一个function包装起来。这个函数能够叫作“测试组assert组装函数”。
这里的测试结果是这个样子的:
看起来更酷了吧。你注意到了没有,这里有一个需求点:若是测试组里面有一个assert测试为false,那么整个测试组也要标记为false。
这个html的结构以下:
每一个测试组用li节点表现,而li节点下又内嵌了一个ul节点,在这个内嵌的ul节点下才是测试组内全部assert的li节点表现。
固然了,有图有真相,这里很明显有一个小bug,"a" + "b" === "ab"明明是正确的,怎么显示的li节点也被画红线了?或许你也能够辩解为既然是整个测试组失败了,那么为这个正确的li节点画红线也说得过去吧?谁让它属于失败的测试组呢?既然选择了猪同样的队友,那就得认命。但是那你又怎么解释这个正确的li节点被一边画了红线,一边却显示为绿色的文字?这明显自相矛盾嘛。
好了,先不理这个小bug了,稍后咱们会解决这个bug。如今仍是让咱们老老实实的来看看这个测试组的功能是如何实现的吧?
html相关的部分都不改,body里仍是那个孤零零的id为rusults的ul节点,css也彻底不用动,须要修改的只有javascript代码。
注意测试组的代码中先调用了一个asserts函数,这个函数负责初始化测试组的一些环境,简单的说它是这个样子的:
// 测试组的总体初始化 function asserts() { // 声明一个results变量, // 做为assert函数闭包和test函数闭包的一部分 var results; // 建立assert表现节点 assert = function(value, desc) { } // 建立测试组表现节点 test = function(name, fn) { } }
这里这里对assert从新进行了赋值,固然咱们首先须要知道这种assert以前没有var声明的,说明这个变量已经在全局的window中,或者将在这句代码执行处被加入到了全局的window中,而咱们上面在说单个assert的时候不是已经有了一个assert函数的实现了吗?那个assert也是在全局的window中的。毫无疑问,在调用asserts函数后,原有的assert函数就被覆盖掉了。test变量也是相似的,在调用asserts函数后,将被加入到全局的window中。
注意asserts函数开始的那个results变量,由于asserts函数调用后会在全局的window增长两个函数assert和test,而这个results变量就必然的成为了这两个函数闭包的一部分。
咱们先看看这个test函数是如何实现的:
test = function(name, fn) { // 找到document中id为results的ul节点元素,就是那个body下的ul results = document.getElementById("results"); // 建立一个ul节点 var ul = document.createElement("ul"); // 建立一个测试组节点,就象建立普通assert节点同样直接调用assert // 毫无心外,这个测试组节点被加到了id为results的ul节点元素下, // 初始默认这个测试组节点的测试结果是true。 // 在测试组节点下添加内嵌的ul节点,该测试组下的全部assert表现节点都会被 // 加入到这个内嵌的ul节点中。 // 既然如此,那么咱们就让results变量指向这个内嵌的ul节点 results = assert(true, name).appendChild(ul); // 调用"测试组assert组装函数",构建各个assert表现节点 fn(); };
在test函数执行的开始,results被指向了body下的ul节点,并在此基础上完成了测试组表现节点的建立,而后results被指向了测试组内嵌的ul节点上,"测试组assert组装函数"被调用,新的assert表现节点就会被加入到results节点下。
下面咱们来看看新的assert函数是如何实现的:
assert = function(value, desc) { // 建立li节点 var li = document.createElement("li"); // 若是value为true,li的class为pass // 若是value为false,li的class为fail li.className = value ? "pass" : "fail"; // 根据desc建立text节点,而后添加到li节点下 li.appendChild(document.createTextNode(desc)); // 把新建的li节点添加到results下,至于这个rusults是啥? // 在test执行前是body下的ul节点 // 在test执行后是测试组表现节点下的ul节点 results.appendChild(li); if (!value) { // 若是有一个assert测试结果是false, // 那么就找到li节点的父节点的父节点, // 也就是测试组表现节点了,而后设置class为fail li.parentNode.parentNode.className = "fail"; } // 返回li节点 // 在test执行前是测试组表现节点 // 在test执行后是assert表现节点 return li; };
好了,搞定,完整的html以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>assert</title> <style> body { font-family: sans-serif; font-size: 12pt; } #results { background-color: #e0e0e0; border-radius: 1em; padding: 1em; list-style-position: inside; } ul { list-style-type : circle; } #results li { margin-bottom: 0.2em; } #results li.fail { color: red; text-decoration: line-through; } #results li.pass { color: green; } </style> <script type="text/javascript"> // 测试组的总体初始化 function asserts() { // 声明一个results变量, // 做为assert函数闭包和test函数闭包的一部分 var results; assert = function(value, desc) { // 建立li节点 var li = document.createElement("li"); // 若是value为true,li的class为pass // 若是value为false,li的class为fail li.className = value ? "pass" : "fail"; // 根据desc建立text节点,而后添加到li节点下 li.appendChild(document.createTextNode(desc)); // 把新建的li节点添加到results下,至于这个rusults是啥? // 在test执行前是body下的ul节点 // 在test执行后是测试组表现节点下的ul节点 results.appendChild(li); if (!value) { // 若是有一个assert测试结果是false, // 那么就找到li节点的父节点的父节点, // 也就是测试组表现节点了,而后设置class为fail li.parentNode.parentNode.className = "fail"; } // 返回li节点 // 在test执行前是测试组表现节点 // 在test执行后是assert表现节点 return li; }; test = function(name, fn) { // 找到document中id为results的ul节点元素,就是那个body下的ul results = document.getElementById("results"); // 建立一个ul节点 var ul = document.createElement("ul"); // 建立一个测试组节点,就象建立普通assert节点同样直接调用assert // 毫无心外,这个测试组节点被加到了id为results的ul节点元素下, // 初始默认这个测试组节点的测试结果是true。 // 在测试组节点下添加内嵌的ul节点,该测试组下的全部assert表现节点都会被 // 加入到这个内嵌的ul节点中。 // 既然如此,那么咱们就让results变量指向这个内嵌的ul节点 results = assert(true, name).appendChild(ul); // 调用"测试组assert组装函数",构建各个assert表现节点 fn(); }; } </script> </head> <body> <ul id="results"></ul> <script type="text/javascript"> asserts(); test("数值计算测试", function() { assert(1 + 1 === 2, "1 + 1 = 2"); }); test("字符串测试", function() { assert("a" + "b" === "ab", '"a" + "b" = "ab"'); assert("a" + "b" === "abc", '"a" + "b" = "abc"'); }); </script> </body> </html>
之因此有这个bug,是由于这里的测试组表现太简单了,直接在li节点上设置class,使得css的可控性不高。学过css列表部分的应该都清楚,对列表的控制应该使用行内文本span嘛。
咱们但愿的显示效果应该是:
相应的html结构应该是:
既然只是将测试组表现节点和测试表现节点多加一层span,那么test函数是彻底不用变的,只是assert函数须要作一点小的修改:
assert = function(value, desc) { // 建立li节点 var li = document.createElement("li"); // 建立sapn节点 var span = document.createElement("span"); // 根据desc建立text节点 var text = document.createTextNode(desc); // 在li下添加span节点 li.appendChild(span); // 在span下添加text节点 // 完成li>span>text的三层关系 span.append(text); // 若是value为true,span的class为pass // 若是value为false,span的class为fail span.className = value ? "pass" : "fail"; // 把新建的li节点添加到results下,至于这个rusults是啥? // 在test执行前是body下的ul节点 // 在test执行后是测试组表现节点下的ul节点 results.appendChild(li); if (!value) { // 若是有一个assert测试结果是false, // 那么就找到li节点的父节点的父节点, // 也就是测试组表现节点了 var liGroup = li.parentNode.parentNode; // 不能直接在测试组表现节点设置class了 // 必须在测试组表现节点下的span节点设置class // 也就是测试组表现节点下的第一个子元素 liGroup.childNodes[0].className = "fail"; } // 返回li节点 // 在test执行前是测试组表现节点 // 在test执行后是assert表现节点 return li; };
相应的css也是须要作些小的修改的,不是直接在li节点上作效果了,而是在span节点上作效果。这些小地方都很容易理解,那么就直接上修改后的完整html吧:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>assert</title> <style> body { font-family: sans-serif; font-size: 12pt; } #results { background-color: #e0e0e0; border-radius: 1em; padding: 1em; list-style-position: inside; } ul { list-style-type : circle; } #results li { margin-bottom: 0.2em; } #results span.fail { color: red; text-decoration: line-through; } #results span.pass { color: green; } </style> <script type="text/javascript"> // 测试组的总体初始化 function asserts() { // 声明一个results变量, // 做为assert函数闭包和test函数闭包的一部分 var results; assert = function(value, desc) { // 建立li节点 var li = document.createElement("li"); // 建立sapn节点 var span = document.createElement("span"); // 根据desc建立text节点 var text = document.createTextNode(desc); // 在li下添加span节点 li.appendChild(span); // 在span下添加text节点 // 完成li>span>text的三层关系 span.append(text); // 若是value为true,span的class为pass // 若是value为false,span的class为fail span.className = value ? "pass" : "fail"; // 把新建的li节点添加到results下,至于这个rusults是啥? // 在test执行前是body下的ul节点 // 在test执行后是测试组表现节点下的ul节点 results.appendChild(li); if (!value) { // 若是有一个assert测试结果是false, // 那么就找到li节点的父节点的父节点, // 也就是测试组表现节点了 var liGroup = li.parentNode.parentNode; // 不能直接在测试组表现节点设置class了 // 必须在测试组表现节点下的span节点设置class // 也就是测试组表现节点下的第一个子元素 liGroup.childNodes[0].className = "fail"; } // 返回li节点 // 在test执行前是测试组表现节点 // 在test执行后是assert表现节点 return li; }; test = function(name, fn) { // 找到document中id为results的ul节点元素,就是那个body下的ul results = document.getElementById("results"); // 建立一个ul节点 var ul = document.createElement("ul"); // 建立一个测试组节点,就象建立普通assert节点同样直接调用assert // 毫无心外,这个测试组节点被加到了id为results的ul节点元素下, // 初始默认这个测试组节点的测试结果是true。 // 在测试组节点下添加内嵌的ul节点,该测试组下的全部assert表现节点都会被 // 加入到这个内嵌的ul节点中。 // 既然如此,那么咱们就让results变量指向这个内嵌的ul节点 results = assert(true, name).appendChild(ul); // 调用"测试组assert组装函数",构建各个assert表现节点 fn(); }; } </script> </head> <body> <ul id="results"></ul> <script type="text/javascript"> asserts(); test("数值计算测试", function() { assert(1 + 1 === 2, "1 + 1 = 2"); }); test("字符串测试", function() { assert("a" + "b" === "ab", '"a" + "b" = "ab"'); assert("a" + "b" === "abc", '"a" + "b" = "abc"'); }); </script> </body> </html>