[javascript] view plaincopy1. if (diff > measure) {
2. num = Math.floor(diff / measure);
3. diff = diff - (num * measure); // BUG: This was missing in our first attempt
4. pieces.push(format(num, consider[i]));
5. }
一旦我们定位并且修正该故障,我们清除所有的console.log调用,避免代码在没有定义console对象的浏览器中崩溃。
分步调试器
Firebug和类似的工具使调试javascript比过去更容易。但是很多人似乎认为console.log是比原始的alert更高级的工具。的确,console不阻塞UI并且更少可能让你强制关闭浏览器,但是console.log调试和alert调试是一样的优雅或不优雅。
一个稍微复杂的方法是使用像Firebug一样的工具使分步调试成为可能。
使用分步调试,你可以通过设置一些断点和检查所有有效值而不是记录每个你想查看的变量的值来节省一些时间。
Console.log的问题
Console.log风格调试有一些问题:首先,console.log有讨厌的引入自身缺陷的风险。如果在演示或部署之前忘记移除最后记录语句,你知道我在说什么。悬浮的记录语句会使你的代码在不支持console对象的浏览器上崩溃,包括Firebug不可用时的火狐。“但是JavaScript是动态的”,我听到你说,“你可以定义你自己的无操作的console,然后问题就会消除”。的确,你可以这样做,但那就像是用刷漆解决你的汽车生锈的问题。
如果悬浮的console.log调用是不可接受的,我们立即认识到下一个问题:它是不可重复的。一旦调试会话结束,你去除了所有的记录语句。如果(当)新问题出现在代码的相同部分时,你又回到了起点,重新采用巧妙的记录语句。分步调试也同样是暂时的。特设调试(Adhoc debugging)是费时的、容易出错的和不可重复的。
更有效的发现缺陷
单元测试是查找缺陷和验证正确性的方法,并且不需要面对调试器临时性和人为console.log/alert调试。单元测试还有其他大量的优势,我将通过这篇文章介绍。
什么是单元测试
单元测试是你的产品代码按照预期结果的可执行部分。例如,假如我们之前在 jQuery.fn.differenceInWords中发现有两个错误没有修正,并试图用单元测试找到它们:
[javascript] view plaincopy1. var second = 1000;
2. var minute = 60 * second;
3. var hour = 60 * minute;
4. var day = 24 * hour;
5.
6. try {
7. // Test that 8 day difference results in "1 week ago"
8. var dateStr = new Date(new Date() - 8 * day).toString();
9. var element = jQuery('Replace me');
10. element.differenceInWords();
11.
12. if (element.text() != "1 week ago") {
13. throw new Error("8 day difference expected\n'1 week ago' got\n'"+
14. element.text() + "'");
15. }
16.
17. // Test a shorter date
18. var diff = 3 * day + 2 * hour + 16 * minute + 10 * second;
19. dateStr = new Date(new Date() - diff).toString();
20. var element = jQuery('Replace me');
21. element.differenceInWords();
22.
23. if (element.text() != "3 days, 2 hours, 16 minutes and 10 seconds ago") {
24. throw new Error("Small date difference expected\n" +
25. "'3 days, 2 hours, 16 minutes and 10 seconds ago' " +
26. "got\n'" + element.text() + "'");
27. }
28.
29. alert("All tests OK!");
30. } catch (e) {
31. alert("Assertion failed: " + e.message);
32. }
上面的测试用例处理具有已知具有时间属性的元素,并在得到的人性化的结果字符串不是我们期望的结果时抛出异常。该代码可以保存到独立的文件或在加载该插件的页面中包含。在一个浏览器中运行会立即让我得到“所有测试正常”或一个指示什么错了的消息。
用这种方法调试你的代码好像很笨拙。我们不仅要写记录语句来帮助我们监测代码,而且我们还不得不用程序创建元素和通过插件运行它们来验证产生的文本。但这种方法有相当多的好处:
该测试可以在任何时间,任何浏览器上重复运行。
无论什么时间当我们改变代码,我们都要记得运行该测试,它可以极大的保证同样的缺陷不会重新回来。
适当的清理,这些测试提供了代码的文档。
测试是自我检查的。无论我们添加了多少测试,我们仍然只有一个页面来验证是否有错误。
测试和产品代码没有冲突,因此不会在作为产品代码的部分发布时带入内部alert和console.log调用的风险。
写该测试带来稍多的初始化效果,但我们只写一次,我们很快的会在下次需要调试同样的代码时节省时间。
使用单元测试框架
刚才我们写的测试包含相当多的套路。幸运的是,已经有很多的测试框架来帮助我们。使用测试框架让我们减少不得不嵌入到测试中的测试逻辑的数量,它进而也减少测试自身的缺陷。框架也可以给我们更多的自动测试和显示结果的选项。