Việc UnitTest trong project Ionic2 sử dung karma và framework jasmine để thực hiện. Chúng ta sẽ tìm hiểu 1 số khái niệm cũng nhưng từng bước xây dựng môi trường UnitTest.
I. Các khái niệm
Karma
Là công cụ thực thi đoạn mã kiểm chứng (test code) Có thể hiểu như là môi trường mà các test code sẽ chạy trện đó. Đưa ra kết quả, thống kê các chỉ số sau khi thực thi test code.
Jasmine
Là một framework viết test code javascript theo mô hình BDD (Behavior-Driven Development)
Gulp
Là công cụ hỗ trợ tự động hóa các task trong quá trình phát triển như: build code, run code, copy files,... Chúng ta sẽ sử dụng gulp để sắp xếp các luồng kiểm chứng.
Browserify
Là công cụ dựa trên nền tảng nodejs, hướng đến giải quyết vấn đề resolve dependencies trong nodejs (thường sử dụng require()
để gọi đến các thư viện). Có thể hiểu là Browserify sẽ tìm các lệnh require()
trong code và tự động tìm các module này, sau đó đóng gói cùng với mã nguồn để đảm bảo mã nguồn luôn có thể chạy được.
CommonJs module
Là các module thỏa mãn các điều kiện sau:
-
Khi tạo ra module thì dùng module.exports hoặc exports
-
CommonJS module, sử dụng hàm require
Tslint
Linting tool có thể giúp developer tối ưu hóa code và viết code chất lượng cao. Linting là một quy trình kiểm tra code tìm lỗi trong code nguồn, và đánh dấu các bug tiềm năng. Có thể hiểu đơn giản thì tslint như một bộ coding convetion. linting (linter) sử dụng kỹ thuật phân tích code tĩnh. Nói cách khác, code được kiểm tra mà không cần phải chạy.
II. Cấu hình môi trường Unit Test
Cài đặt dependency và typings
`* browserify
- browserify-istanbul
- codecov.io
- gulp-tslint
- gulp-typescript
- isparta
- jasmine-core
- jasmine-spec-reporter
- karma
- karma-browserify
- karma-chrome-launcher
- karma-coverage
- karma-jasmine
- karma-mocha-reporter
- karma-phantomjs-launcher
- phantomjs-prebuilt
- protractor
- tsify
- ts-node
- tslint
- tslint-eslint-rules
- typescript
- typings`
Chi tiết nội dung của dependency thì các bạn tự tìm hiểu thêm nhé. Trong nội dung lần này tôi chỉ hướng dẫn cách cấu hình thôi.
Bạn có thể cài đặt thông qua từng dòng lệnh
npm install -g typings
npm install --save-dev browserify-istanbul codecov.io gulp-tslint gulp-typescript isparta jasmine-core karma karma-browserify karma-chrome-launcher karma-coverage karma-jasmine karma-mocha-reporter karma-phantomjs-launcher phantomjs-prebuilt traceur tsify ts-node tslint
typings install --save --global registry:dt/jasmine registry:dt/node
Hoặc hãy cấu hình vào package.json
. Và sử dụng npm để install lại. Chẳng hạn như thế này.
"devDependencies": {
"browserify": "13.0.1",
"browserify-istanbul": "2.0.0",
"codecov.io": "0.1.6",
"del": "2.2.1",
"gulp": "3.9.1",
"gulp-tslint": "5.0.0",
"gulp-typescript": "2.13.6",
"gulp-watch": "4.3.8",
"ionic-gulp-browserify-typescript": "2.0.0",
"ionic-gulp-fonts-copy": "1.0.0",
"ionic-gulp-html-copy": "1.0.0",
"ionic-gulp-sass-build": "1.0.0",
"ionic-gulp-scripts-copy": "2.0.1",
"isparta": "4.0.0",
"jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0",
"karma": "1.1.0",
"karma-browserify": "5.0.5",
"karma-chrome-launcher": "1.0.1",
"karma-coverage": "1.0.0",
"karma-jasmine": "1.0.2",
"karma-mocha-reporter": "2.0.4",
"karma-phantomjs-launcher": "1.0.1",
"phantomjs-prebuilt": "2.1.7",
"protractor": "3.3.0",
"run-sequence": "1.2.1",
"ts-node": "0.9.3",
"tsify": "0.16.0",
"tslint": "3.12.1",
"tslint-eslint-rules": "1.3.0",
"typings": "1.3.1"
},
$ npm install
Cấu hình gulp
Đặt file cấu hình./test/gulpfile.ts
. Cấp độ bằng với cấp độ root của dự án. Đây là một phần của trong quy đinh của Angular 2 Style Guide . Tuy nhiên bạn có thể để ở bất kỳ thư mục nào mà bạn muốn. Nhưng hãy nhớ rằng, khi bạn sử dụng lệnh gulp thì hãy cấu hình để trỏ đến file này.
gulp --gulpfile test/gulpfile.ts --cwd ./ unit-test
Sửa Karma’s static
Để có thể output ra việc chạy test thông qua html hãy copy tạo thêm thư 2 file context.html và debug.html vào node_modules/karma/static. Nếu không thiết lập 2 file này thì không thể chạy được PhantomJS.
context.html
<!DOCTYPE html>
<!--
This is the execution context.
Loaded within the iframe.
Reloaded before every execution run.
-->
<html>
<head>
<title></title>
<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>
<ion-app></ion-app>
<!-- The scripts need to be in the body DOM element, as some test running frameworks need the body
to have already been created so they can insert their magic into it. For example, if loaded
before body, Angular Scenario test framework fails to find the body and crashes and burns in
an epic manner. -->
<script src="context.js"></script>
<script type="text/javascript">
// Configure our Karma and set up bindings
%CLIENT_CONFIG%
window.__karma__.setupContext(window);
// All served files with the latest timestamps
%MAPPINGS%
</script>
<!-- Dynamically replaced with <script> tags -->
%SCRIPTS%
<script type="text/javascript">
window.__karma__.loaded();
</script>
</body>
</html>
debug.html
<!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>
%X_UA_COMPATIBLE%
<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>
<ion-app></ion-app>
<!-- 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 src="context.js"></script>
<script src="debug.js"></script>
<script type="text/javascript">
// Configure our Karma
%CLIENT_CONFIG%
// All served files with the latest timestamps
%MAPPINGS%
</script>
<!-- Dynamically replaced with <script> tags -->
%SCRIPTS%
<script type="text/javascript">
window.__karma__.loaded();
</script>
</body>
</html>
Linting
Mặc định linting được cấu hình trong khi chạy unit test. Khi quá trình phân tích chưa đạt thì sẽ không thực thi các test code.
File Cấu hình của lint được đặt trong tslint.json
. Bạn có thể tham khảo 1 số cú pháp quy định của lint
"rules": {
"align": [
true,
"parameters",
"arguments",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": false,
"eofline": true,
"forin": false,
"indent": [
true,
"spaces"
],
"interface-name": false,
"jsdoc-format": true,
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
180
]
Để tắt chức năng linting thì chỉ cần xóa bỏ lint trong gulpfile.ts
// build unit tests, run unit tests, remap and report coverage
gulp.task('unit-test', (done: Function) => {
runSequence(
['clean'], // Ionic's clean task, nukes the whole of www/build
['lint', 'html'],
'karma',
(<any>done)
);
});
Lệnh unit test
node_modules/gulp/bin/gulp.js --gulpfile test/gulpfile.ts --cwd ./ unit-test
Để đơn giản hơn thì có thể cài đặt vào package.json
"scripts": {
"karma": "gulp --gulpfile test/gulpfile.ts --cwd ./ karma-debug"
"test": "gulp --gulpfile test/gulpfile.ts --cwd ./ unit-test"
}
Lần sau khi chỉ cần thực thi npm test
là đã có thể thực thi unit test.
III) Thực thi Unit Test
Cấu trúc thư mục
app/
├── app.html
├── app.spec.ts
├── app.ts
├── components
│ ├── clickerButton
│ │ ├── clickerButton.html
│ │ ├── clickerButton.spec.ts
│ │ └── clickerButton.ts
│ └── clickerForm
│ ├── clickerForm.html
│ ├── clickerForm.spec.ts
│ └── clickerForm.ts
├── models
│ ├── clicker.spec.ts
│ ├── clicker.ts
│ ├── click.spec.ts
│ └── click.ts
├── pages
│ ├── clickerList
│ │ ├── clickerList.html
│ │ ├── clickerList.scss
│ │ ├── clickerList.spec.ts
│ │ └── clickerList.ts
│ └── page2
│ ├── page2.html
│ ├── page2.scss
│ └── page2.ts
└── services
├── clickers.spec.ts
├── clickers.ts
├── utils.spec.ts
└── utils.ts
test
├── gulpfile.ts
├── karma.config.js
├── testUtils.ts
└── karma-static
├── context.html
└── debug.html
Độ bao phủ code (Test Coverage)
Bạn có thể xem mức độ coverage trên trình duyệt bằng cách access vào thư mục /path/to/[appname]/coverage/lcov-report/
Test debug
Nếu bạn đã cấu hình script ở bước trên hãy sử dụng npm run karma để thực thi chế độ debug.
Một số khái niệm và toán tử của jasmine
-
Spec Suite
- Là một bộ kịch bản test, bao gồm nhiều hàm, mỗi hàm test tập trung vào một nội dung cần kiểm tra của chương trình. Spec Suite được định nghĩa bởi hàm describe().
-
Khai báo hàm test (Spec)
- Trong Jasmine, mỗi hàm kiểm thử được gọi là một Spec. Jasmine cung cấp hàm it() để định nghĩa Spec.
- Chúng ta có thể phân nhỏ các Spec Suite bằng cách định nghĩa các Spec Suite lồng nhau.
-
Toán tử so sánh
berforeEach
vàafterEach
để khai báo, xóa những biến, hàm sẽ được chạy trước (sau) khi chạy từng specexpect(x).toEqual(val)
: Khẳng định giá trị của đối tượng x bằng với val (nhưng không nhất thiết đồng nhất nhau).expect(x).toBe(obj)
: Khẳng định rằng đối tượng x đồng nhất với obj (2 đối tượng này là một).expect(x).toMatch(regexp)
: Khẳng định chuỗi x khớp với biểu thức chính quy regexp.expect(x).toBeNull()
: Khẳng định rằng biến x chứa giá trị là null.expect(x).toBeTruthy()
: Khẳng định rằng giá trị x là true hoặc ước lượng bằng true.expect(x).toBeFalsy()
: Khẳng định rằng giá trị x là false hoặc ước lượng bằng false.(x).toContain(y)
: Khẳng định rằng x là một chuỗi ký tự và x chứa giá trị y (chuỗi y là một phần của chuỗi x).expect(x)
.toBeGreaterThan(y): Khẳng định rằng x lớn hơn y.expect(x)
toBeDefined(): Khẳng định rằng biến x đã được định nghĩa.expect(x)
.toBeUndefined(): Khẳng định rằng biến x chưa được định nghĩa. `