Karma:1. 集成 Karma 和 Jasmine 进行单元测试
关于 Karma 会是一个系列,讨论在各种环境下,使用 Karma 进行单元测试。
本文讨论 karma 集成 Jasmine 进行单元测试。
初始化 NPM
实现初始化 NPM 包管理,创建 package.json 项目管理文件。
使用参数 -y 直接按照默认值创建 packgae.json 项目管理文件。
PS C:\study\mykarma> npm init -y Wrote to C:\study\mykarma\package.json: { "name": "mykarma", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
现在,可以在项目文件夹中看到 package.json 文件已经创建了。
安装 Karma
现在,可以直接使用 NPM 来安装 karma。
i 是 install 命令的缩写,-D 是 --save-dev 的缩写。
PS C:\study\mykarma> npm i -D karma npm WARN package.json mykarma@1.0.0 No description npm WARN package.json mykarma@1.0.0 No repository field. npm WARN package.json mykarma@1.0.0 No README data npm WARN optional dep failed, continuing fsevents@1.0.7 karma@0.13.21 node_modules\karma ├── batch@0.5.3 ├── di@0.0.1 ├── graceful-fs@4.1.3 ├── rimraf@2.5.2 ├── mime@1.3.4 ├── colors@1.1.2 ├── source-map@0.5.3 ├── isbinaryfile@3.0.0 ├── bluebird@2.10.2 ├── dom-serialize@2.2.1 (custom-event@1.0.0, void-elements@2.0.1, extend@3.0.0, ent@2.2.0) ├── http-proxy@1.13.2 (eventemitter3@1.1.1, requires-port@1.0.0) ├── optimist@0.6.1 (wordwrap@0.0.3, minimist@0.0.10) ├── glob@7.0.0 (path-is-absolute@1.0.0, inherits@2.0.1, once@1.3.3, inflight@1.0.4) ├── useragent@2.1.8 (lru-cache@2.2.4) ├── minimatch@3.0.0 (brace-expansion@1.1.3) ├── lodash@3.10.1 ├── expand-braces@0.1.2 (array-unique@0.2.1, array-slice@0.2.3, braces@0.1.5) ├── log4js@0.6.31 (semver@4.3.6, readable-stream@1.0.33) ├── connect@3.4.1 (utils-merge@1.0.0, parseurl@1.3.1, debug@2.2.0, finalhandler@0.4.1) ├── core-js@2.1.0 ├── body-parser@1.15.0 (content-type@1.0.1, bytes@2.2.0, depd@1.1.0, raw-body@2.1.5, debug@2.2.0, qs@6.1.0, iconv-lite@0.4.13, http-errors@1.4.0, on-finished@2.3.0, type-is@1.6.11) ├── socket.io@1.4.5 (debug@2.2.0, has-binary@0.1.7, socket.io-parser@2.2.6, socket.io-adapter@0.4.0, engine.io@1.6.8, socket.io-client@1.4.5) └── chokidar@1.4.2 (path-is-absolute@1.0.0, inherits@2.0.1, async-each@0.1.6, glob-parent@2.0.0, is-binary-path@1.0.1, is-glob@2.0.1, readdirp@2.0.0, anymatch@1.3.0)
PS C:\study\mykarma>
现在我们可以使用 node 来运行 karma 了。
>node ./node_modules/karma/bin/karma
为了能在命令行直接执行 karma 命令,我们再按着一个 karma-cli.
-g 表示全局安装,这样可以在系统的任何文件夹中直接执行 karma 命令。
PS C:\study\mykarma> npm i -g karma-cli C:\Users\XXX\AppData\Roaming\npm\karma -> C:\Users\XXX\AppData\Roaming\npm\node_modules\karma-cli\bin\karma karma-cli@0.1.2 C:\Users\guanjun\AppData\Roaming\npm\node_modules\karma-cli └── resolve@1.1.7
安装之后,可以直接使用 karma 来启动测试了,首先检查一下当前的版本。
PS C:\study\mykarma> karma --version Karma version: 0.13.21 PS C:\study\mykarma>
祝贺你, 基本的 Karma 已经安装成功了。
安装 Jasmine 和 chrome-launcher
我们使用 Karma 来驱动单元测试,所以只有 Karma 是不行的,还需要安装单元测试库以便运行测试脚本,安装测试库与 Karma 的适配器,还有各种浏览器的适配器。
这里我们安装 Jasmine 的测试支持和 chrome 浏览器的适配器。
对于 jasmine 来说,我们需要 Jasmine 的适配器,还必须有 jasmine-core 库。
karma-chrome-launcher 则提供了 karma 与 chrome 的适配器。
PS C:\study\mykarma> npm i -D jasmine-core karma-jasmine karma-chrome-launcher npm WARN package.json mykarma@1.0.0 No description npm WARN package.json mykarma@1.0.0 No repository field. npm WARN package.json mykarma@1.0.0 No README data npm WARN peerDependencies The peer dependency jasmine-core@* included from karma-jasmine will no npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly. jasmine-core@2.4.1 node_modules\jasmine-core karma-jasmine@0.3.7 node_modules\karma-jasmine karma-chrome-launcher@0.2.2 node_modules\karma-chrome-launcher ├── fs-access@1.0.0 (null-check@1.0.0) └── which@1.2.4 (isexe@1.1.2, is-absolute@0.1.7)
现在涉及单元测试的基本工具已经安装就绪了。
Karma 的命令
karma 支持三个命令。
- start [<configFile>] [<options>] 启动 Karma 持续执行,也可以执行单次的测试,然后直接收集测试结果.
- init [<configFile>] 初始化配置文件.
- run [<options>] [ -- <clientArgs>] Trigger a test run.
创建 karma 配置文件
Karma 需要进行配置,配置文件比较复杂,可以使用 karma 提供的 init 命令来直接创建基础的配置文件。在处理过程中,我们可以使用交互方式提供测试的信息,Karma 根据这些信息生成一个基本的配置文件。配置文件的默认名称是 karma.conf.js。如果你提供了配置文件的名称,karma 会将配置信息写入到你提供的文件名中。
创建 Karma 配置文件
PS C:\study\mykarma> karma init Which testing framework do you want to use ? Press tab to list possible options. Enter to move to the next question. > jasmine Do you want to use Require.js ? This will add Require.js plugin. Press tab to list possible options. Enter to move to the next question. > no Do you want to capture any browsers automatically ? Press tab to list possible options. Enter empty string to move to the next qu ion. > Chrome > What is the location of your source and test files ? You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js". Enter empty string to move to the next question. > src/**/*.js 20 02 2016 22:32:26.698:WARN [init]: There is no file matching this pattern. > test/**/*.spec.js 20 02 2016 22:33:26.513:WARN [init]: There is no file matching this pattern. > Should any of the files included by the previous patterns be excluded ? You can use glob patterns, eg. "**/*.swp". Enter empty string to move to the next question. > Do you want Karma to watch all the files and run the tests on change ? Press tab to list possible options. > yes Config file generated at "C:\study\mykarma\karma.conf.js". PS C:\study\mykarma>
由于我们没有提供配置文件名称,这里生成的是默认的配置文件 karma.conf.js 。
启动 Karma
由于已经有了 karma 配置文件,现在可以使用 karma start 启动 karma 了,由于还没有测试,所以看不到测试结果是正常的。
需要注意的是 karma 配置中的 singleRun 这个参数,设置为 false 的话,karma 会自动监控测试环境,默认是 Chrome, 如果你关掉了,karma 会自动重新启动一个。如果配置为 true,执行一次测试之后,karma 会自动停掉。
在 singleRun 为 false 的情况下,执行的结果可能是这样的。
PS C:\study\mykarma> karma start 22 02 2016 10:54:11.796:INFO [karma]: Karma v0.13.21 server started at http://localhost:9876/ 22 02 2016 10:54:11.806:INFO [launcher]: Starting browser Chrome 22 02 2016 10:54:13.206:INFO [Chrome 47.0.2526 (Windows 7 0.0.0)]: Connected on socket /#rbiYFxG0uTVJxpVoAAAA with id 13601272
单元测试
成功和失败
现在我们可以使用 Jasmine 开始写测试了。
在项目文件夹中,创建一个名为 test 的子文件夹来保存测试用例。然后在 test 文件夹中创建一个 unit 的文件夹来保存单元测试用例。
在这个文件夹中创建一个名为 hello.spec.js 的测试文件。
一般来说,我们会为测试用例的文件名称提供一个特定的模式,以便对测试用例进行统一处理,这里我们约定测试用例的文件名以 .spec.js 为结尾。
hello.spec.js
describe('hello, unit test.', function(){ it('should also be able to test', function(){ expect(true).toBe(true); }); it('should be failed', function(){ expect(true).toBe(false); }) });
这个测试包含了两个测试用例,一个一定成功,一个一定失败。
确认在我们 karma 的配置文件中,包含了我们的测试用例。
// list of files / patterns to load in the browser files: [ 'test/**/*.spec.js' ],
现在,使用 karma start 启动测试,在控制台应该会看到如下的输出。
PS C:\study\mykarma> karma start 22 02 2016 11:09:31.137:WARN [karma]: No captured browser, open http://localhost:9876/ 22 02 2016 11:09:31.157:INFO [karma]: Karma v0.13.21 server started at http://localhost:9876/ 22 02 2016 11:09:31.167:INFO [launcher]: Starting browser Chrome 22 02 2016 11:09:32.561:INFO [Chrome 47.0.2526 (Windows 7 0.0.0)]: Connected on socket /#ymfXfb-xI2a3fZ82AAAA with id 31292195 Chrome 47.0.2526 (Windows 7 0.0.0) hello, unit test. should be failed FAILED Expected true to be false. at Object.<anonymous> (C:/study/mykarma/test/unit/hello.spec.js:7:20) Chrome 47.0.2526 (Windows 7 0.0.0): Executed 2 of 2 (1 FAILED) (0.016 secs / 0.006 secs)
可以看到执行了两个测试,其中一个失败了,失败的测试为 hello, unit test 中的 should be failed 测试用例。
测试实际的代码
在项目文件夹中,创建一个名为 src 的子文件夹来保存我们的应用代码,在其中创建一个名为 add.js 的脚本文件,我们将来测试它的工作是否正确。
function add(a, b){ return a + b; }
这个脚本非常简单,仅仅用来计算两个数字之后,没有任何的验证。
然后,我们针对它写两个测试用例,保存到 ./test/unit/add.spec.js 文件中。
describe('add function unit test.', function(){ it('2 + 3 = 5', function(){ var result = add( 2, 3 ); expect( result ).toBe( 5 ); }); it('2 + 3 = 6, this should faild.', function(){ var result = add( 2, 3 ); expect(result).toBe( 6 ); }) });
确认你的 karma 配置文件中,包含了被测试代码和测试代码。
// list of files / patterns to load in the browser files: [ 'src/**/*.js', 'test/**/*.spec.js' ],
现在的控制台输出应该是这样的。
PS C:\study\mykarma> karma start 22 02 2016 11:22:18.800:WARN [karma]: No captured browser, open http://localhost:9876/ 22 02 2016 11:22:18.810:INFO [karma]: Karma v0.13.21 server started at http://localhost:9876/ 22 02 2016 11:22:18.820:INFO [launcher]: Starting browser Chrome 22 02 2016 11:22:20.232:INFO [Chrome 47.0.2526 (Windows 7 0.0.0)]: Connected on socket /#i6GaDaxNTy8HWL52AAAA with id 89457157 Chrome 47.0.2526 (Windows 7 0.0.0) add function unit test. 2 + 3 = 6, this should faild. FAILED Expected 5 to be 6. at Object.<anonymous> (C:/study/mykarma/test/unit/add.spec.js:9:24) Chrome 47.0.2526 (Windows 7 0.0.0) hello, unit test. should be failed FAILED Expected true to be false. at Object.<anonymous> (C:/study/mykarma/test/unit/hello.spec.js:7:20) Chrome 47.0.2526 (Windows 7 0.0.0): Executed 4 of 4 (2 FAILED) (0.021 secs / 0.007 secs)
祝贺你,现在你已经可以测试你的代码了!
如果你的应用是由一个一个独立的函数定义出来的,现在就已经可以进行测试了。
Karma 执行原理
在 Karma 启动的浏览器界面中,可以看到当前的执行状态。
点击 DEBUG 按钮,可以进入实际的测试页面。
这个页面看起来是空白的,但是执行了实际的测试脚本,进入浏览器的开发者模式,可以看到实际的内容。比如,我们上面的实际执行内容。
查看页面源码,可以看到这个 Karma 生成的页面。
<!doctype html> <!-- This file is almost the same as context.html - loads all source files, but its purpose is to be loaded in the main frame (not within an iframe), just for immediate execution, without reporting to Karma server. --> <html> <head> <title>Karma DEBUG RUNNER</title> <link href="favicon.ico" rel="icon" type="image/x-icon" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> </head> <body> <!-- The scripts need to be at the end of body, so that some test running frameworks (Angular Scenario, for example) need the body to be loaded so that it can insert its magic into it. If it is before body, then it fails to find the body and crashes and burns in an epic manner. --> <script type="text/javascript"> window.__karma__ = { info: function(info) { if (info.dump && window.console) window.console.log(info.dump); }, complete: function() { if (window.console) window.console.log('Skipped ' + this.skipped + ' tests'); }, store: function() {}, skipped: 0, result: window.console ? function(result) { if (result.skipped) { this.skipped++; return; } var msg = result.success ? 'SUCCESS ' : 'FAILED '; window.console.log(msg + result.suite.join(' ') + ' ' + result.description); for (var i = 0; i < result.log.length; i++) { window.console.error(result.log[i]); } } : function() {}, loaded: function() { this.start(); } }; window.__karma__.config = {"args":[],"useIframe":true,"captureConsole":true,"clearContext":true}; // All served files with the latest timestamps window.__karma__.files = { '/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js': '391e45351df9ee35392d2e5cb623221a969fc009', '/base/node_modules/karma-jasmine/lib/boot.js': '4a7da64f416169520c9d5c43b5a7feac6bde9104', '/base/node_modules/karma-jasmine/lib/adapter.js': 'd76809fbd57147a108ceb7fe2c134b2d39806a9a', '/base/src/add.js': 'dd99cc5693226f200581da90d5f231a95e6bb720', '/base/test/unit/add.spec.js': 'f430471235f184ab5e13c14ccb87740b833487d6', '/base/test/unit/hello.spec.js': '5b7173f9c7e05f6aadc798a5065cd6dc572d005d' }; </script> <!-- Dynamically replaced with <script> tags --> <script type="text/javascript" src="/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script> <script type="text/javascript" src="/base/node_modules/karma-jasmine/lib/boot.js"></script> <script type="text/javascript" src="/base/node_modules/karma-jasmine/lib/adapter.js"></script> <script type="text/javascript" src="/base/src/add.js"></script> <script type="text/javascript" src="/base/test/unit/add.spec.js"></script> <script type="text/javascript" src="/base/test/unit/hello.spec.js"></script> <script type="text/javascript"> window.__karma__.loaded(); </script> </body> </html>
关键的内容在页面的后部。
<!-- Dynamically replaced with <script> tags --> <script type="text/javascript" src="/base/node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script> <script type="text/javascript" src="/base/node_modules/karma-jasmine/lib/boot.js"></script> <script type="text/javascript" src="/base/node_modules/karma-jasmine/lib/adapter.js"></script> <script type="text/javascript" src="/base/src/add.js"></script> <script type="text/javascript" src="/base/test/unit/add.spec.js"></script> <script type="text/javascript" src="/base/test/unit/hello.spec.js"></script> <script type="text/javascript"> window.__karma__.loaded(); </script>
在这个页面,我们可以在源码中设置断点,检查测试。
总结
可以看到,使用 Karma 集成 Jasmine 测试是很方便的组合。