第二章 利用测试和调试武装自己
调试代码
调试 javascript 有两个重要的方法:日志记录和断点
日志记录
使用console.log()
方法,在控制台查看日志消息。
断点
可以在断点处随意查看任意代码的状态,包括:所有可访问的变量、上下文以及作用域链。
测试用例生成
优秀的测试用例具有三个重要特征:
- 可重复性(repeatability)——测试结果应该是高度可再生的。多次运行测试应该产生相同的结果。
- 简单性(simplicity)——测试应该只专注于测试一件事。在不影响测试用例目的的情况下,应尽可能消除过多的HTML标记、CSS或JavaScript。
- 独立性(independence)——测试用例应该独立执行。避免一个测试结果依赖于另一个测试结果。
构建测试的主要方法分别是:
- 解构型测试:在消弱代码隔离问题时进行创建,以消除任何不恰当的问题。
- 构建型测试:从一个大家熟知的良好精简场景开始,构建用例,直到能够重现bug为止。
测试框架
根据测试需要,可以从javascript测试框架中找到很多功能,包括:
- 能够模拟浏览器行为(单击按键等)。
- 测试的交互式控制(暂停和恢复测试)。
- 处理异步测试超时问题。
- 能够过滤哪些会被执行的测试。
测试套件基础知识
测试套件的主要目的是聚合代码中的所有单个测试,将其组合成为一个单位,这样就可以批量运行,提供一个可以轻松反复运行的单一资源。
断言
单元测试框架的核心是断言方法,通常叫assert()。该方法接收一个值——需要断言的值,以及一个表示该断言目的的描述。如果该值结果为true则断言通过,否则,断言失败。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>断言的简单实现</title>
<style>
/*定义结果样式*/
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<!--显示测试结果-->
<ul id="results"></ul>
<script>
// 定义assert方法
function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
document.getElementById("results").appendChild(li);
}
// 使用断言执行测试
window.onload = function() {
assert(true, "测试running...");
assert(false, "失败!");
}
</script>
</body>
</html>
测试组
执行单元测试时,一个测试组可能代表一组断言,因为它们在我们的API或程序里关联的是一个单一的方法。如果做行为驱动开发,测试组将通过任务集成断言。在实践中动态控制层级被证明是非常有用的(如果测试失败的话,可以收缩/展开测试组,并且过滤测试)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试分组的实现</title>
<style>
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<ul id="results"></ul>
<script>
(function(){
var results;
this.assert = function assert(value,desc){
var li = document.createElement('li');
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
results.appendChild(li);
if(!value){
li.parentNode.parentNode.className = "fail";
}
return li;
};
this.test = function test(name,fn){
results = document.getElementById("results");
results = assert(true,name).appendChild(document.createElement("ul"));
fn();
};
})();
window.onload = function (){
test("A test", function(){
assert(true, "First assertion completed");
assert(true, "Second assertion completed");
assert(true, "Third assertion completed");
});
test("Another test", function(){
assert(true, "First assertion completed");
assert(false, "Second assertion failed");
assert(true, "Third assertion completed");
});
test("A third test", function(){
assert(null, "fail");
assert(5, "pass");
})
}
</script>
</body>
</html>
异步测试
处理该问题的方式通常是过度设计,并且设计的要比实际需要的更复杂。处理异步测试,需要遵循的简单步骤:
- 将依赖相同异步操作的断言组合成一个统一的测试组。
- 每个测试组需要放在一个队列上,在先前其他的测试组完成运行之后再运行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>简单的异步测试套件</title>
<style>
li.pass{color: green;}
li.fail{color: red;}
</style>
</head>
<body>
<ul id="results"></ul>
<script>
(function(){
var queue = [], paused = false, results;
// test 接收一个包含多个断言的函数,这些断言可以是同步的,也可以是异步的——并放置在队列中等待执行
this.test = function(name,fn){
queue.push(function(){
results = document.getElementById("results");
results = assert(true,name).appendChild(
document.createElement("ul"));
fn();
});
runTest();
};
// pause 应该在test内部调用,告诉该测试套件暂停执行测试,直到测试组完成
this.pause = function(){
paused = true;
};
// resume 恢复测试,经过延迟后开始下一个测试的运行,避免出现长时间运行的代码块
this.resume = function(){
paused = false;
setTimeout(runTest,1);
};
// 在测试排队时从队列中移除时进行调用。用于检查当前套件目前是否没被暂停以及队列中是否有测试任务,一旦满足情况,将从队列中取出一个测试并尝试执行它
// 测试组完成执行后,runTest 会检查该套件目前是否暂停了,如果没暂停(这意味着,测试组中只有异步测试),runTest 将开始执行下一组测试
function runTest(){
if(!paused && queue.length){
queue.shift()();
if(!paused){
resume();
}
}
}
this.assert = function assert(value,desc){
var li = document.createElement("li");
li.className = value ? "pass" : "fail";
li.appendChild(document.createTextNode(desc));
results.appendChild(li);
if(!value){
li.parentNode.parentNode.className = "fail";
}
return li;
};
})();
window.onload = function(){
test("Async Test #1", function(){
pause();
setTimeout(function(){
assert(true, "First test completed");
resume();
},1000);
});
test("Async Test #2", function(){
pause();
setTimeout(function(){
assert(true, "Second test completed");
resume();
},1000);
});
}
</script>
</body>
</html>