Upload
mitsuru-ogawa
View
1.086
Download
2
Embed Size (px)
Citation preview
Angular2-rc.1Unit testing Overview
2016/05/17 mg-mtg#7 Mitsuru Ogawa(@mitsuruog)
Who am I?
- 小川 充(@mitsuruog)- Front-end engineer@Givery- works
- Angular1.4- ユニットテスト、E2Eテスト
- フロントエンドのCI
Today’s motivation and goal
− Motivation
- Release Candidateが近いので、そろそろテストを書くノウハウが必要
- Angular1のテスト資産をどうするべきか、が心配になってきた
- Goal
- Angular2でのテストについての概要を知る
- Angular1のテスト資産の戦略的生かし方
Table contents
1. Angular2ユニットテストの基本1.1. ツールとセットアップ
1.2. Pipeのテスト
1.3. Serviceのテスト
1.4. Componentのテスト
2. Angular1からの変更点
3. 最近のアップデート情報
4. Angular1テスト資産の生存戦略
以前話した内容の拡大版です
http://goo.gl/1SpphI
1.Angular2ユニットテストの基本
ツールとセットアップ
- Typescript- Karma- Jasmine- SystemJS
Karma
SystemJS(モジュールローダー)
SpecApplication
Karma.conf.js =>
Karma-test.shim.js =>
Angular2ユニットテストスタック
Pipeのテスト
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'sayHello'})export class SayHelloPipe implements PipeTransform { transform(value:string):string { return `Hello ${value}`; }}
Pipeのテスト
import { describe, it, expect, beforeEach } from '@angular/testing';import { SayHelloPipe } from './say-hello.pipe';
テストで利用するモジュールをimportする
Pipeのテスト
import { describe, it, expect, beforeEach } from '@angular/testing';import { SayHelloPipe } from './say-hello.pipe';
describe('Test: SayHelloPipe', () => {
let testee; beforeEach(() => { testee = new SayHelloPipe(); });
});
インスタンスを取得する
Pipeのテスト
import { describe, it, expect, beforeEach } from '@angular/testing';import { SayHelloPipe } from './say-hello.pipe';
describe('Test: SayHelloPipe', () => {
let testee; beforeEach(() => { testee = new SayHelloPipe(); });
it('should say hello', () => { expect(testee.transform('world')).toEqual('Hello world'); });
});
テストを実行して結果を比較する
Serviceのテストimport {Injectable} from "@angular/core";
@Injectable()
export class SayHelloService { constructor() {} say():string { return 'Hello'; }}
Serviceのテストimport {describe, it, inject, expect, beforeEachProviders} from '@angular/core/testing';import {SayHelloService} from "./say-hello.service";
テストで利用するモジュールをimportする
Serviceのテストimport {describe, it, inject, expect, beforeEachProviders} from '@angular/core/testing';import {SayHelloService} from "./say-hello.service";
describe('Test: SayHelloService', () => {
beforeEachProviders(() => [ SayHelloService ]);
});
後でServiceをDIする際に利用するprovidorを指定する。(何もOverrideせずそのまま利用する設定)
Serviceのテストimport {describe, it, inject, expect, beforeEachProviders} from '@angular/core/testing';import {SayHelloService} from "./say-hello.service";
describe('Test: SayHelloService', () => {
beforeEachProviders(() => [ SayHelloService ]);
it('Should say Hello', inject([SayHelloService], (testee:SayHelloService) => { }));});
injectでテストコンテキストにserviceをDIする
Serviceのテストimport {describe, it, inject, expect, beforeEachProviders} from '@angular/core/testing';import {SayHelloService} from "./say-hello.service";
describe('Test: SayHelloService', () => {
beforeEachProviders(() => [ SayHelloService ]);
it('Should say Hello', inject([SayHelloService], (testee:SayHelloService) => { expect(testee.say()).toEqual('Hello'); }));});
テストを実行して結果を比較する
Serviceのテストimport {describe, it, inject, expect, beforeEachProviders} from '@angular/core/testing';import {SayHelloService} from "./say-hello.service";
import {provide} from "@angular/core";import {SayHelloServiceMock} from "./say-hello.service.mock";
describe('Test: SayHelloService', () => {
beforeEachProviders(() => [ provide(SayHelloService, { useClass: SayHelloServiceMock }) ]);
});
providorを書き換えることで、ServiceをOverrideすることもできる(SayHelloServiceMockでOverrideしている)
BONUS
Componentのテストimport {Component, OnInit} from "@angular/core";import {SayHelloService} from "./say-hello.service";
@Component({ selector: 'say-hello', template: '<div>Hello</div>', providers: [SayHelloService]})export class SayHelloComponent implements OnInit { constructor(private service:SayHelloService) {}
ngOnInit() { console.log(this.service.say()); }}
Componentのテスト①import { describe, it, inject, async, expect, beforeEachProviders } from '@angular/core/testing';import { TestComponentBuilder, ComponentFixture } from '@angular/compiler/testing';import { Component } from '@angular/core';
import { SayHelloComponent } from './say-hello.component';
テストで利用するモジュールをimportする
Componentのテスト①import { describe, it, inject, async, expect, beforeEachProviders } from '@angular/core/testing';import { TestComponentBuilder, ComponentFixture } from '@angular/compiler/testing';import { Component } from '@angular/core';
import { SayHelloComponent } from './say-hello.component';
@Component({ selector: 'test-container', template: '<say-hello></say-hello>', directives: [SayHelloComponent]})
class TestComponent {}
テストfixtureを作成する
Componentのテスト②describe('Test: SayHelloComponent', () => { let builder; beforeEach(inject([TestComponentBuilder], (tcb) => {
builder = tcb; }));
});
TestComponentBuilderを初期化してCacheしておく
Componentのテスト②describe('Test: SayHelloComponent', () => { let builder; beforeEach(inject([TestComponentBuilder], (tcb) => {
builder = tcb; }));
it('Should display Hello', async(() => { return builder.createAsync(TestComponent) .then((fixture: ComponentFixture<TestComponent>) => {
}); }));});
TestComponentを初期化してfixtureを取得する
Componentのテスト②describe('Test: SayHelloComponent', () => { let builder; beforeEach(inject([TestComponentBuilder], (tcb) => {
builder = tcb; }));
it('Should display Hello', async(() => { return builder.createAsync(TestComponent) .then((fixture: ComponentFixture<TestComponent>) => { let div = fixture.nativeElement.querySelector('div');
expect(div).toHaveText('Hello'); }); }));});
fixtureからHTML Elementを取得して結果を比較する
Componentのテスト②describe('Test: SayHelloComponent', () => { let builder; beforeEach(inject([TestComponentBuilder], (tcb) => {
builder = tcb; }));
it('Should display Hello', async(() => { let template = '<say-hello></say-hello>'; return builder.overrideTemplate(TestComponent, template) .createAsync(TestComponent)
.then((fixture: ComponentFixture<TestComponent>) => { let div = fixture.nativeElement.querySelector('div'); expect(div).toHaveText('Hello'); }); }));});
fixtureのtemplateは別のものに切り替え可能です
BONUS
2.Angular1からの変更点
https://goo.gl/lQ4s02
詳しくはこちらへ =>
Angular1からの変更点
- Jasmine- describe, it, matcher, spy, mock …
- Dependency injection => inject- 注入方法:_moduleName_の奇妙なルール
- HttpMock => $httpbackend- TestFixtureの作成 =>$compile- 変更検知
- digestループ
- scope.$digest()
- モジュールのロード
- module
- モジュールの書き換え
- $provide
- Jasmine- 独自拡張したngMatcherが存在
- Dependency injection => inject- 注入方法:Typescriptによる型
- HttpMock => MockBackend- TestFixtureの作成 => TestComponentBuilder
- 変更検知
- Change Detection
- fixture.detectChanges()
- モジュールのロード
- import
- モジュールの書き換え
- beforeEachProviders
Angular1からの変更点
- Jasmine- describe, it, matcher, spy, mock …
- Dependency injection => inject- 注入方法:_moduleName_の奇妙なルール
- HttpMock => $httpbackend- TestFixtureの作成 =>$compile- 変更検知
- digestループ
- scope.$digest()
- モジュールのロード
- module
- モジュールの書き換え
- $provide
- Jasmine- 独自拡張したngMatcherが存在
- Dependency injection => inject- 注入方法:Typescriptによる型
- HttpMock => MockBackend- TestFixtureの作成 => TestComponentBuilder
- 変更検知
- Change Detection
- fixture.detectChanges()
- モジュールのロード
- import
- モジュールの書き換え
- beforeEachProviders
小さい
大きい
3.最近のアップデート(テスト周辺)
https://www.ng-conf.org
- @angular/core/testing- describe, it, inject, etc…
- @angular/compiler/testing- TestComponentBuilder
- @angular/platform-browser/testing
パッケージ名の変更
- angular2/testing- describe, it, inject, etc...- TestComponentBuilder
- angular2/platform/testing/browser
RCBeta.17
Test APIの改善
- 非同期処理、変更検知が格段に良くなった
- [RC] injectAsync => async
- [Bata.16] autoDetectChanges() の導入
- 詳しくはJulie Ralphのプレゼンをチェック
- Zone.js!!Zone.js!!Zone.js!!
Testing you Tasks: Julie Ralphhttps://youtu.be/DltUEDy7ItY
4.Angular1テスト資産の生存戦略
基本方針
- 移行ではなく「移植」
- 残せるものを残す。残せる部分を増やす。
- 厳しい冬を想定したテストコードの剪定。
- Viewに依存しない「純粋なロジック」のみを移植
- ViewはComponentで書き直すケースが多くなる。
- Directiveに代表されるComponentとして再利用できるものは移植OK。
ユニットテストコード移植パス
filter pipe
providor, service, factory service
directive
controller
directive
ユニットテストコード移植パス
filter pipe
providor, service, factory service
directive
controller
directive
Controllerテストコード生存戦略
- Controller Helper Service パターン- 純粋なロジックをController Helper Serviceに移動、これを将来移植する
- 既存のテストコードはController Helper Service に適用しておく
angular.module('app') .controller('AppController', function() { var vm = this; this.doSomething = function () { // 何か移植する価値がありそうなロジック
}; });
angular.module('app') .controller('AppController', function(AppControllerHelper) { var vm = this; this.doSomething = AppControllerHelper.doSomething;
});
angular.module('app') .factory('AppControllerHelper', function() { return { doSomething: function () { // 何か移植する価値がありそうなロジック
}; });
Controllerテストコード移植パス
Controller
ControllerHelper Service
Component
ロジックを分離
移植パス
注入(DI)
移行
移植
Controllerテストコード移植パス
移植パス
注入(DI)
Controller
ControllerHelper Service
Componentテスト
テスト
テスト
移行テスト
新規作成
ロジック部分を移植
まとめ
まとめ
- ユニットテストに関するAngular1の知見は再利用できる
- Typescriptとzone.jsによりテストコードが書きやすくなっている
- Angular1のテスト資産は「移植」する心意気で
- 特にControllerのテストコードは注意!!
参考リンク
- Angular 2 unit testing overview- http://www.slideshare.net/mitsuruogawa33/angular-2-unit-testing-overview
- Angular 1 to 2 Quick Reference In unit testing- https://gist.github.com/mitsuruog/9e3e5c2c5d17a15a4c2a
- Testing you Tasks: Julie Ralph- https://youtu.be/DltUEDy7ItY
- mitsuruog/_angular2-unit-test-sample- https://github.com/mitsuruog/_angular2-unit-test-sample
- mitsuruog/angular2-minimum-starter: Minimum starter kit for angular2- https://github.com/mitsuruog/angular2-minimum-starter
- I am mitsuruog - Angular2 unit test- https://goo.gl/3ypfaH