Unit Testing trong project Ionic2

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.

8 min read

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

  • berforeEachafterEach để khai báo, xóa những biến, hàm sẽ được chạy trước (sau) khi chạy từng spec
  • expect(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. `

Bài viết liên quan