4.2 组织测试用例
Jasmine使用describe、it等全局函数组织测试用例。
4.2.1 describe
describe是Jasmine的全局函数,用于创建一个测试套件(test suite)。测试套件可以理解为一组测试用例(test case或spec)的集合。
describe函数接受两个参数(一个字符串和一个回调函数)。字符串参数是这个测试套件的名字或标题(通常描述被测试内容),回调函数是实现测试套件的代码块(称为describe块)。示例代码如下:
describe('Player', function() { /* tests */ });
describe函数可以嵌套使用(测试套件可以包含测试套件),例如:
describe('Player', function() { describe('when new', function() { /* tests */ }); });
4.2.2 it
it也是Jasmine的全局函数,用来在describe块里创建一个测试用例(spec)。和describe一样,it接受两个参数(一个字符串和一个回调函数),字符串参数是测试用例的名字或标题,回调函数是实现测试用例的代码块(称为it块)。示例代码如下:
describe('Player', function() { it('should be able to play a Song', function() { /* code and assertions */ }); it('should be able to pause a Song', function() { /* code and assertions */ }); });
describe和it函数的字符串参数很重要。describe创建的测试套件用来组织多个相关的测试用例。通过拼接测试套件的名字和测试用例的名字,可以完整描述一个测试场景,方便在大型项目中进行查找。如果描述得当的话,你的测试可以以自然语言的方式表达出来,形成文档。
describe块和it块都是JavaScript函数,所以JavaScript的作用域(scope)规则也适用于此处。在describe块里声明的变量可以被它内部任何it块使用。
describe('Player', function() { var a; it('and so is a spec', function() { a = true; }); });
4.2.3 安装和拆卸
在真正执行测试代码之前,通常需要做大量的铺垫,例如准备一些测试数据,建立测试场景,这些为了成功测试而做的准备工作称为Test Fixture。测试完毕后需要释放运行测试占用的资源。这些铺垫工作占据的代码可能随着测试复杂度的增加而增加。为了避免在每个测试用例里重复这些代码,测试框架一般都会提供安装(Setup)和拆卸(Teardown)函数。
Jasmine提供了4个全局函数用于安装和拆卸,如表4-3所示。
表4-3 安装和拆卸函数
这些函数接受一个回调函数作为参数,执行相关的安装代码和拆卸代码。例如:
describe('Player', function() { var player; beforeEach(function() { player = new Player(); }); afterEach(function() { /* cleanup code */ }); it('should be able to play a Song', function() { /* code and assertions */ }); });
因为describe可以嵌套,所以测试用例可以定义在任何一层describe块里,如图4-7所示。
图4-7 嵌套的describe
理解安装和拆卸函数在嵌套describe情况下的执行顺序,有助于合理组织测试用例。
例如使用下面这个例子:
describe('Jasmine Execution Sequence', function () { beforeAll(function () { console.log('outer beforeAll'); }); beforeEach(function () { console.log('outer beforeEach'); }); it('spec 1', function () { console.log('spec 1'); }); console.log('statement 1'); describe('inner', function () { beforeAll(function () { console.log('inner beforeAll'); }); afterAll(function () { console.log('inner afterAll'); }); console.log('statement 3'); beforeEach(function () { console.log('inner beforeEach'); }); it('spec 3', function () { console.log('spec 3'); }); afterEach(function () { console.log('inner afterEach'); }); }); it('spec 2', function () { console.log('spec 2'); }); console.log('statement 2'); afterEach(function () { console.log('outer afterEach'); }); afterAll(function () { console.log('outer afterAll'); }); });
输出结果如下:
statement 1 statement 3 statement 2 outer beforeAll outer beforeEach spec 1 outer afterEach inner beforeAll outer beforeEach inner beforeEach spec 3 inner afterEach outer afterEach inner afterAll outer beforeEach spec 2 outer afterEach outer afterAll
以上示例有这样的输出结果是因为:
• Jasmine会先执行describe块的代码,然后再执行beforeAll、beforeEach和it函数。所以“statement 1”“statement 3”“statement 2”首先被输出。
• describe块的代码从上到下依次执行。尽管console.log('statement 2')在外层describe块里,但是它还是排在内层describe块的console.log('statement 3')后面执行。
• beforeAll会在它所在describe块的测试用例和beforeEach执行前执行,而且只执行一次。
• beforeEach会在它所在describe块和内层describe块里的测试用例执行前被执行。所以outer beforeEach会在外层的测试用例spec 1之前执行,也会在内层的测试用例spec 3之前执行。而inner beforeEach只会在spec 3之前执行。
• 在每个测试用例执行前,Jasmine会从最外层的describe块开始,顺序执行每个beforeEach,直到这个测试用例所在的describe块为止。所以在执行测试用例spec 3之前,Jasmine先执行outer beforeEach,然后执行inner beforeEach。
• 测试用例会从上到下依次执行。虽然spec 2在外层,但是它还是在内层的测试用例spec 3后面执行。
• 测试用例执行完后,Jasmine会执行测试用例所在describe块的afterEach,然后依次执行外层的afterEach,直至最外层describe块。例如在spec 3测试用例完成后,inner beforeEach会先被执行,然后是outer afterEach。
• afterAll会在它所在describe块的测试用例和afterEach执行后执行,而且只执行一次。
4.2.4 禁用测试套件和挂起测试用例
有时候用户希望某些测试用例在单元测试时不被执行,因为这些测试用例还未完成,或者执行时间比较长。这时可以使用Jasmine提供的xdescribe函数来禁用测试套件,屏蔽被禁用的测试套件内的所有测试用例,使这些被禁用的测试用例不会出现在最终测试报告里。示例代码如下:
xdescribe('Player', function() { /* disabled tests */ });
测试用例也可以被挂起(pending)。和禁用不同,挂起的测试用例不会被执行,但是测试报告里会显示处于挂起状态的测试用例的名字。
有3种挂起测试用例的方式。
(1)使用xit函数,例如:
xit('can be declared "xit"', function () { /* code and assertions */ });
(2)使用it函数创建测试用例时,只提供第1个字符串名字,不提供第2个回调函数。例如:
it('can be declared with "it" but without a function');
(3)在it代码块的任意地方调用pending函数。例如:
it('can be declared by calling "pending" in the body', function () { /* code and assertions */ pending('this is why it is pending'); });
测试报告会显示挂起的测试用例,如图4-8所示。
图4-8 挂起的测试用例