Home > Article > Web Front-end > JavaScript component journey (4): Testing JavaScript components_javascript skills
本期,我们要讨论的话题是 JavaScript 的测试,以检查组件的状态和工作方式是否符合预期,还会介绍一个可以方便编写测试用例的测试方法。这里说的测试当然是使用自动化的测试手段,这是软件质量保证(QA)的重要环节。就本系列文章介绍的 Smart Queue 来说,我们的测试目标包括:
对于第一个目标,只需检查对象创建后的属性是否符合预期即可。我们已经多次提到“符合预期”,断言(Assert)正是为此而设计的。简单的说,断言就是确保所测试的表达式结果为“真”,否则,以某种方式通知测试人员,并帮助其定位断言失败的测试用例。
第二个目标稍稍有点复杂。由于我们在组件编码实现的时候,将排序后的队列(<font face="新宋体">_sorted</font>
)隐藏在了闭包中,所以外部是无法访问的。有两种方法可以考虑:(1)重构代码,增加代码的可测试性,又有两种重构方法:(a)设置 debug 开关,打开时将 <font face="新宋体">_sorted</font>
暴露给外部;(b)增加独立文件,以构建的方式拼接代码最终生成一个测试版本。(2)测试行为的结果而不是过程,前一种方法实质上是深入到组件的运行时状态,而这个方法只是检查组件的运行结果。本期选用后一种种测试方式,第一种测试方式留给有兴趣的读者练习:)
需要说明的是,我个人不赞成第一种的方法a. 为什么呢?我先说一下这个任务队列的设计理念:
<font face="新宋体">SmartQueue.Queue = [[], [], []]</font>
, 结果是外部可以访问到队列项。代码仅供介绍之用,你可以安全地删除 <font face="新宋体">SmartQueue.Queue = </font>
来达成安全控制。 回到刚才讨论的话题,设置 debug 开关后,任务信息就潜在的泄漏可能性。进一步地,继续改造代码也可以达成在使用 debug 开关时的安全性,做法是将开关的控制放在 SmartQueue 的构造函数中,这样要求 SmartQueue 实现 Singleton 模式(见上一篇文章);一旦创建对象后,不允许修改闭包内的 debug 标记。
在编写具体测试代码前,我们设计了一个测试方法,以简化测试代码(主要是用例)的编写。简单地说,就是将测试用例与测试本身的代码分离——前者以语义良好的方式编写,后者是一次性编写,用于处理前者设定的测试用例。用例编写者需要写格式形如这样的代码:
<span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">ul</SPAN><SPAN style="COLOR: #b000b0"> </SPAN><SPAN style="COLOR: #0000c0">id</SPAN><SPAN style="COLOR: #b000b0">=</SPAN><SPAN style="COLOR: #008000">"J_test_cases"</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">pre</SPAN><SPAN style="COLOR: #b000b0">></span>task = new sq.Task({fn: function() { log('unamed') }})<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">pre</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">ul</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>typeof task.fn === 'function'<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>task.name === 't0'<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>task.level === 1<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>task.dependencies.length === 0<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>task.context == window<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">ul</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">pre</SPAN><SPAN style="COLOR: #b000b0">></span>task = new sq.Task({fn: function() { log('unamed') }, name: 'hello'})<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">pre</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">ul</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>task.name === 'hello'<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"><</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span>task.level === 1<span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">ul</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">li</SPAN><SPAN style="COLOR: #b000b0">></span> <span style="COLOR: #b000b0"></</SPAN><SPAN style="COLOR: #c00000">ul</SPAN><SPAN style="COLOR: #b000b0">></span>
ul li pre (CSS 选择器路径,下同)中写要测试的代码,相当于前置操作;ul ul li 中对这个代码进行断言测试,可以编写多条断言。这里建议对基本数据类型使用 <font face="新宋体">===</font>
和 <font face="新宋体">!===</font>
运算符以加强对数据类型的预期判断。
接下来,我们编写两个 helper 方法用来输出和测试:
<span style="COLOR: #b000b0">function</span> log(str) { node.value += str + <span style="COLOR: #008000">'</span><span style="COLOR: #606060">\n</span><span style="COLOR: #008000">'</span>; } <span style="COLOR: #b000b0">function</span> assert(expression) { <span style="COLOR: #0000c0">var</span> flag; eval(<span style="COLOR: #008000">'flag = '</span> + expression); <span style="COLOR: #c00000">return</span> <span style="COLOR: #c00000">typeof</span>(flag) === <span style="COLOR: #008000">'boolean'</span> && flag; }
<font face="新宋体">log</font>
用来向文本框追加信息,<font face="新宋体">assert</font>
用来测试传入表达式的值。测试方法如下(这里使用了 jQuery):
<span style="COLOR: #0000c0">var</span><span style="COLOR: #606060"> sq = SmartQueue, task, total = </span><span style="COLOR: #008000">0</span><span style="COLOR: #606060">, passed = </span><span style="COLOR: #008000">0</span><span style="COLOR: #606060">, failed = </span><span style="COLOR: #008000">0</span><span style="COLOR: #606060">;</span> <span style="COLOR: #606060">$</span>(<span style="COLOR: #008000">'#J_test_cases'</span><span style="COLOR: #606060">).children</span>(<span style="COLOR: #606060">).each</span>(<span style="COLOR: #b000b0">function</span>(index)<span style="COLOR: #606060"> </span>{ eval($(<span style="COLOR: #008000">'pre'</span>, <span style="COLOR: #0000c0">this</span>).text()); task.register(); $(<span style="COLOR: #008000">'li'</span>, <span style="COLOR: #0000c0">this</span>).each(<span style="COLOR: #b000b0">function</span>() { <span style="COLOR: #0000c0">var</span> item = $(<span style="COLOR: #0000c0">this</span>); <span style="COLOR: #0000c0">var</span> flag = assert(item.text()); <span style="COLOR: #c00000">if</span>(flag) passed ++; <span style="COLOR: #c00000">else</span> failed ++; item.prepend((flag ? <span style="COLOR: #008000">'<font color="green">[PASS]</font>'</span> : <span style="COLOR: #008000">'<font color="red">[FAIL]</font>'</span>) + <span style="COLOR: #008000">' '</span>); total++; }).wrap(<span style="COLOR: #008000">'<pre class="brush:php;toolbar:false">'); }).end().before('
Total: '
+ total + ', passed: '+ passed +', failed: ' + failed + ''); sq.fire();这个结构还可改进一下,比如输出测试说明而不是具体的代码,也可以增加后置操作,这里就不再演示了。你还可以查看完整的测试页面,含有 23 个测试用例和完整的测试实现。
~~~~~~~~~~~~~ Bagua Dividing Line ~~~~~~~~~~~~~
Okay, we’ve come to the end of this series of articles experiencing the joy of thinking and doing, but this is just the beginning. We have gone through the entire process of implementing a small practical component and experienced the excitement of the JavaScript world. Let us move on~