Jasmine是一个javascript的测试框架, 可以用来单元测试, 也可以进行其它类型的测试.
通常, 我们在项目比较大时会" 不得不引入测试", 原因很简单,因为这样在添加新的功能或者修复bug时不会引入新的bug,
这样会免去很多调试的时间,少做不少无用功。
谷歌的angularjs框架就是采用的测试驱动开发(TDD)方法,即先写需要的新功能的测试,然后再写代码。把写代码和调试代码的顺序
刚好反了过来。
言归正传,我们开始说Jasmine。
首先,我们在计算机上部署一个Jasmine,先让它跑起来
我们假定你正在使用ubuntu 14.04, 并且已经安装了git, 如果没有的话, 请运行
sudo apt-get install git
接下来,运行
cd ~
git clone https://github.com/pivotal/jasmine.git
这样会下载整个jasmine的工程,
然后我们再建立另外一个文件夹jasmine-test-prj来研究jasmine
mkdir ~/jasmine-test-prj
cd ~/jasmine-test-prj
接下来我们把jasmine源代码里dist文件夹下的jasmine-standalone-2.0.0.zip文件复制过来
mkdir jasmine
cp ~/jasmin/dist/jasmine-standalone-2.0.0.zip .
cd jasmine
unzip jasmine-standalone-2.0.0.zip
这样我们就得到了我们需要的文件,先来看看都有什么。
这个目录包含了jasmine的源代码和示例,那我们先运行一下示例。
google-chrome ~/jasmine-test-prj/jasmine/specrunner.html
这样我们发现打开的网页会自动运行一个已有示例。
下面我们就可以真正地开始使用jasmine这个框架了。
到目前为止,我们的目录结构应该是这样的, 如果不是的话, 请按照下面的图示建立一个相同的
jasmine-test-prj
--jasmine
然后我们建立一个js文件夹来存放我们的待测代码,再建立一个test文件夹来存放我们的测试代码
cd ~/jasmine-test-prj
mkdir js test
touch js/math.js test/test.html
然后在js文件夹下面先写一个简单的待测函数,在math.js文件中,内容如下
function add(a, b) {
return a + b;
};
这只是一个hello world!式的文件, 后面我们会按照实际需求添加其它功能和测试
然后我们的test文件夹下面的test.html如下
our huge math lib unit test
在上面的文件中, head部分大概包括了三个重要的块,一是jasmine本身需要的文件,二是待测文件,三是测试文件。
我们的math.js就在第二部分,而math-spec.js是我们用来测试math.js的文件。
下面我们来编写math-spec.js文件
touch ~/jasmine-test-prj/test/math-spec.js
该文件内容如下
describe('math', function() {
it('should add', function() {
expect(add(3, 5)).toBeCloseTo(8);
});
});
然后,我们运行
google-chrome ~/jasmine-test-prj/test/test.html
congratulations! 我们成功了!
在上面那个测试文件中,我们可以按字面意思阅读,就是描述math,它应该做加法,希望把3和5加起来,结果等于8
在这里,toBeCloseTo用来做精确的数学式比较,例如比较1.0001和1.002是不是相等。
现在假设我们要写一个在项目中用到的数学库,它叫Math, 它应该定义一个类叫CoolMath类,然后有一个静态的公有方法叫add
现在我们用TDD的方式来开发,那么先来写测试,我们的math-spec.js经过修改,应该变成下面这样:
describe('our greatest and largest and complexed super math lib', function() {
it('should has CoolMath class', function() {
expect(CoolMath).toBeDefined();
});
it('should add', function() {
expect(CoolMath.add(3, 5)).toBeCloseTo(8);
});
});
然后我们刷新浏览器, 发现测试没有通过,当然,我们还没写代码呢!上面的测试的意思是要有个math类,并且已经定义,
使用了toBeDefined,如果我们希望某个变量没有定义,可以使用not.toBeDefined();很好理解吧?
那么, 我们对math.js做一些修改,如下
function CoolMath() {};
CoolMath.add = function(a, b) {
return a + b;
};
现在我们的数学库是一个函数对象,叫Math,然后它有一个公用的静态方法,就是加法。然后再刷新浏览器,通过了测试!
到这里,你会感觉每次刷新浏览器太麻烦了,那么建议你使用karma,我之前写过一篇介绍karma的文章。
再下来,我们又有了另外一种需求,我们想计算复数的加法,甚至我们想让我们的数学库支持自定义的加法,从而实现这个功能
同样我们先写测试:
describe('our greatest and largest and complexed super math lib', function() {
it('should has CoolMath class', function() {
expect(CoolMath).toBeDefined();
});
it('should add', function() {
expect(CoolMath.add(3, 5)).toBeCloseTo(8);
});
it('should custom add', function() {
expect(CoolMath.setCustomAdd).toBeDefined();
function anAdd() {};
CoolMath.setCustomAdd(anAdd);
expect(CoolMath.addCustom).toEqual(anAdd);
});
});
在上面的测试中, 我们希望数学库有一个添加自定义加法的接口,然后使用了这个接口,最后判断这个接口设置的函数是不是我们所需要的。
在这里使用了toEqual来比较对象是否相等,可以是两个object,然后计算每个属性是否都一样,从而给出结果。
我们math.js代码如下
function CoolMath() {};
CoolMath.add = function(a, b) {
return a + b;
};
CoolMath.setCustomAdd = function(cb) {
this.addCustom = cb;
};
哈哈,通过了测试!这样以来,我们新加入了功能,确丝毫没有影响已有的功能,也没有什么bug.
接下来,我们想产生一个自然数的数列,同样先写测试
describe('our greatest and largest and complexed super math lib', function() {
it('should has CoolMath class', function() {
expect(CoolMath).toBeDefined();
});
it('should add', function() {
expect(CoolMath.add(3, 5)).toBeCloseTo(8);
});
it('should custom add', function() {
expect(CoolMath.setCustomAdd).toBeDefined();
function anAdd() {};
CoolMath.setCustomAdd(anAdd);
expect(CoolMath.addCustom).toEqual(anAdd);
});
it('should generate a natural number sequence', function() {
var ns = CoolMath.genNNS(1,3);
expect(ns).toEqual([1,2,3]);
});
});
这里,我们可以看到,toEqual是可以直接运用于数组的。我们希望产生1,2,3的数组,但是只写这个测试是不够的,如果我传入的参数是负数或者小数呢?
所以,我们要再加入一个测试来确保这个函数的健壮性:
describe('our greatest and largest and complexed super math lib', function() {
it('should has CoolMath class', function() {
expect(CoolMath).toBeDefined();
});
it('should add', function() {
expect(CoolMath.add(3, 5)).toBeCloseTo(8);
});
it('should custom add', function() {
expect(CoolMath.setCustomAdd).toBeDefined();
function anAdd() {};
CoolMath.setCustomAdd(anAdd);
expect(CoolMath.addCustom).toEqual(anAdd);
});
it('should generate a natural number sequence', function() {
var ns = CoolMath.genNNS(1,3);
expect(ns).toEqual([1,2,3]);
});
it('should throw exception when arguments are illegal', function() {
expect(function() {
CoolMath.genNNS(-1,3);
}).toThrow();
});
});
这里我们得到了一种测试函数抛出异常的方法,即建立一个匿名函数,然后在其中进行可以抛出异常的调用。
然后我们增加功能,完成错误检查:
function CoolMath() {};
CoolMath.add = function(a, b) {
return a + b;
};
CoolMath.setCustomAdd = function(cb) {
this.addCustom = cb;
};
CoolMath.genNNS = function(s, e) {
if(s < 0 || e < 0 || s > e){
throw "need positive number!";
}
var arr = [];
for(var i = s; i <=e; i++){
arr.push(i);
}
return arr;
};
到目前为止,常用jasmine函数有toEqual, toBeDefined, toFalsy, toTruthy, 这些函数前可以加not, 例如not.toBeFalsy(), 意味希望得到一个true;
现在我们运行测试后,生成的测试结果是这样的:
我们的代码写到这里,会发现虽然只有5个测试,但是代码在逻辑上已经比较凌乱了,这是代码洁癖的人无法接受的。我们现在有2个测试来测试
和加法相关的, 还有两个测试来测数列,还有一个保证mathlib存在。在逻辑上这4个测试应该分成三部分,一部分是加法,一部分是数列,其它测试在另外一部分。所以,我们修改math-spec.js:
describe('our greatest and largest and complexed super math lib', function() {
it('should has CoolMath class', function() {
expect(CoolMath).toBeDefined();
});
describe("add", function() {
it('should add', function() {
expect(CoolMath.add(3, 5)).toBeCloseTo(8);
});
it('should custom add', function() {
expect(CoolMath.setCustomAdd).toBeDefined();
function anAdd() {};
CoolMath.setCustomAdd(anAdd);
expect(CoolMath.addCustom).toEqual(anAdd);
});
});
describe('genNNS', function() {
it('should generate a natural number sequence', function() {
var ns = CoolMath.genNNS(1,3);
expect(ns).toEqual([1,2,3]);
});
it('should throw exception when arguments are illegal', function() {
expect(function() {
CoolMath.genNNS(-1,3);
}).toThrow();
});
});
});
在上面的代码中,加法和数列的功能测试被放入了分开的块中,我们使用describe函数完成了测试代码逻辑上的分割,也证明describe是可以嵌套的。
此外,我们的测试结果也变了: